Population Health Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
Global Conventions
| Convention | Value |
|---|---|
| Base path (Kong) | /api/v1/population-health |
| Auth | Authorization: Bearer <Keycloak RS256 JWT> |
| Content-Type | application/json |
| Pagination | page (1-based), pageSize (max 100, default 20) |
| Tenant scoping | Extracted from JWT tenantId claim; cross-tenant = 403 |
| ID format | ULID with service prefix (e.g., coh_, reg_, ors_) |
| Timestamps | ISO-8601 UTC |
Error Envelope
{
"statusCode": 400,
"code": "INVALID_COHORT_DSL",
"message": "Expression tree node at path '.children[1]' has unknown operator 'gtr'",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736"
}
Error Code Reference
| HTTP | Code | Meaning |
|---|---|---|
| 400 | INVALID_COHORT_DSL | Cohort expression tree failed schema validation |
| 400 | INVALID_QUERY_PARAMS | Malformed query parameters |
| 401 | UNAUTHORIZED | Missing or invalid JWT |
| 403 | ACCESS_DENIED | Insufficient role/permission |
| 403 | CONSENT_REQUIRED | Secondary-use consent not recorded |
| 403 | CROSS_TENANT_SCOPE_VIOLATION | Requested scope outside authorized tenant |
| 403 | MODULE_NOT_LICENSED | Tenant does not have population-health licensed |
| 404 | COHORT_NOT_FOUND | Cohort ID not found |
| 404 | RESOURCE_NOT_FOUND | Generic resource not found |
| 409 | REFRESH_JOB_ACTIVE | Cohort refresh already running |
| 409 | EXPORT_JOB_ACTIVE | Duplicate export job for same period |
| 422 | DEIDENT_K_THRESHOLD_VIOLATION | k-anonymity threshold not met; export suppressed |
| 422 | INVALID_OUTREACH_TRANSITION | FSM state transition not allowed |
| 429 | EXPORT_RATE_LIMIT | Export requests exceed tenant quota |
| 503 | DHIS2_UNAVAILABLE | HMIS DHIS2 endpoint unreachable |
Resource Group: Dashboard
GET /api/v1/population-health/dashboard
Returns aggregate population health metrics for the caller's authorized scope.
Auth scopes: population_health:dashboard:read
Query parameters:
| Param | Type | Required | Description |
|---|---|---|---|
from | ISO-8601 | No | Lower bound for encounter/event window |
to | ISO-8601 | No | Upper bound |
facilityId | ULID | No | Filter to facility |
departmentId | ULID | No | Filter to department |
providerId | ULID | No | Filter to provider |
nodeId | ULID | No | Node scope override (must be authorized) |
Response 200:
{
"asOf": "2026-04-18T06:00:00Z",
"dataFreshness": "2026-04-18T05:45:00Z",
"activePatients": 142800,
"ageDistribution": [
{ "band": "0-4", "count": 12100 },
{ "band": "5-17", "count": 28400 },
{ "band": "18-44","count": 54200 },
{ "band": "45-64","count": 33800 },
{ "band": "65+", "count": 14300 }
],
"genderDistribution": [
{ "gender": "female", "count": 71900 },
{ "gender": "male", "count": 70300 },
{ "gender": "other", "count": 600 }
],
"highRiskCount": 7240,
"screeningCompliance": { "overall": 0.71, "byType": [] },
"immunizationCoverage": { "overall": 0.83 },
"registryPrevalence": [
{ "registryType": "diabetes", "count": 8200 },
{ "registryType": "hypertension", "count": 14100 }
]
}
Resource Group: Cohorts
POST /api/v1/population-health/cohorts
Create a new cohort definition.
Auth scopes: population_health:cohort:write
Request body:
{
"name": "Diabetes adults overdue A1C",
"description": "Adults 18+ with T2DM whose last A1C is >180 days ago",
"definition": {
"op": "AND",
"children": [
{ "field": "condition.code", "operator": "in", "value": ["E11", "E13"] },
{ "field": "age.years", "operator": ">=", "value": 18 },
{ "field": "lab.a1c.daysSinceResult", "operator": ">", "value": 180 }
]
},
"isShared": true,
"refreshPolicy": "schedule",
"refreshCronExpr": "0 2 * * *"
}
Response 201:
{
"id": "coh_01HX...",
"version": 1,
"status": "Draft",
"createdAt": "2026-04-18T07:00:00Z"
}
Errors: 400 INVALID_COHORT_DSL, 403 MODULE_NOT_LICENSED
GET /api/v1/population-health/cohorts
List cohorts visible to caller.
Auth scopes: population_health:cohort:read
Query parameters: page, pageSize, q (name search), ownerId, sharedOnly (boolean), status
Response 200:
{
"data": [ { "id": "coh_01HX...", "name": "...", "status": "Active", "membershipCount": 1240 } ],
"total": 14,
"page": 1,
"pageSize": 20
}
GET /api/v1/population-health/cohorts/:id
Get cohort metadata, version, and membership summary.
Auth scopes: population_health:cohort:read
Response 200: Full CohortDto including definition, lastEvaluatedAt, membershipCount.
Errors: 404 COHORT_NOT_FOUND
POST /api/v1/population-health/cohorts/:id/refresh
Trigger asynchronous membership recomputation.
Auth scopes: population_health:cohort:write
Response 202:
{ "jobId": "job_01HX...", "status": "queued", "estimatedCompletionMs": 30000 }
Errors: 409 REFRESH_JOB_ACTIVE (returns existing jobId)
DELETE /api/v1/population-health/cohorts/:id
Archive (soft-delete) a cohort.
Auth scopes: population_health:cohort:delete
Response: 204 No Content. Errors: 404 COHORT_NOT_FOUND
Resource Group: Disease Registries
GET /api/v1/population-health/registries/:type
List disease registry members and their clinical control status.
Path param type: tb | malaria | mch | diabetes | hypertension | copd | asthma | chf | ckd | mental-health | obesity
Auth scopes: population_health:registry:read
Query parameters:
| Param | Type | Description |
|---|---|---|
riskTier | low|medium|high|critical | Filter by risk tier |
controlStatus | string | e.g., controlled, uncontrolled |
facilityId | ULID | Facility scope |
providerId | ULID | Provider scope |
page, pageSize | int | Pagination |
Response 200: List envelope with RegistryEntryDto rows (patientId, riskTier, controlStatus, lastVisitAt, lastLabAt, activeGaps).
Note: PHI (patientId) returned only to roles with phi:read permission.
Resource Group: Screenings
GET /api/v1/population-health/screenings/compliance
Screening compliance aggregates and drill-down keys.
Auth scopes: population_health:screening:read
Query parameters: screeningType, from, to, facilityId, providerId
Response 200:
{
"asOf": "2026-04-18T06:00:00Z",
"items": [
{ "screeningType": "colorectal-cancer", "due": 320, "overdue": 180, "completed": 2400, "complianceRate": 0.74 }
]
}
Resource Group: Immunizations
GET /api/v1/population-health/immunizations/coverage
Immunization coverage by age band and vaccine type.
Auth scopes: population_health:immunization:read
Query parameters: ageBand, vaccineCode, facilityId, nodeId
Response 200: Coverage rates, overdue counts, refusal counts, contraindication counts.
Resource Group: Risk Scores
POST /api/v1/population-health/risk/score
Trigger risk scoring for a cohort or specific patient set.
Auth scopes: population_health:risk:write
Request body:
{
"modelKey": "clinical-risk-v1",
"scope": "cohort",
"scopeId": "coh_01HX...",
"asOf": "2026-04-18T00:00:00Z"
}
Response 202:
{ "jobId": "rsk_01HX...", "status": "queued" }
PATCH /api/v1/population-health/risk/scores/:id/override
Apply manual risk tier override.
Auth scopes: population_health:risk:override
Request body:
{ "overrideTier": "high", "overrideReason": "Complex multi-morbidity per MDT review" }
Response 200: Updated RiskScoreDto.
Resource Group: Outreach
POST /api/v1/population-health/outreach/lists
Generate an outreach list from a cohort and care-gap filters.
Auth scopes: population_health:outreach:write
Request body:
{
"name": "April Overdue Hypertension Follow-up",
"cohortId": "coh_01HX...",
"channels": ["phone", "sms"],
"assignedTeamId": "team_01HX..."
}
Response 201: OutreachListDto with id, itemCount, status: generating.
PATCH /api/v1/population-health/outreach/items/:id
Update outreach item status and log attempt.
Auth scopes: population_health:outreach:write
Request body:
{ "status": "contacted", "notes": "Patient agreed to follow-up next Tuesday" }
Response 200: Updated OutreachItemDto. Errors: 422 INVALID_OUTREACH_TRANSITION
Resource Group: Quality Metrics
GET /api/v1/population-health/quality-metrics
Quality metric snapshots and trend series.
Auth scopes: population_health:quality:read
Query parameters: program (hedis|qof|ohip-qip|moph-custom|donor), metricKey, from, to
Response 200:
{
"data": [
{
"metricKey": "hedis-bp-control",
"program": "hedis",
"period": { "start": "2026-01-01", "end": "2026-03-31" },
"numerator": 820, "denominator": 1020, "exclusions": 70,
"rate": 0.8039,
"trend": [ { "period": "2025-Q4", "rate": 0.77 }, { "period": "2026-Q1", "rate": 0.80 } ]
}
]
}
Resource Group: HMIS Exports
POST /api/v1/population-health/hmis/exports
Trigger on-demand HMIS DHIS2 export (supplement to scheduled runs).
Auth scopes: population_health:hmis:export (platform admin or MoPH integration role)
Request body:
{
"indicatorFamily": "service-utilization",
"period": { "start": "2026-04-01", "end": "2026-04-17" }
}
Response 202:
{ "jobId": "hmis_01HX...", "status": "queued" }
Errors: 409 EXPORT_JOB_ACTIVE, 503 DHIS2_UNAVAILABLE
Resource Group: Research / De-Identified Exports
POST /api/v1/population-health/exports
Request a de-identified aggregate or cohort export for secondary use.
Auth scopes: population_health:export:write + secondary_use:approved
Request body:
{
"exportType": "cohort-deidentified",
"cohortId": "coh_01HX...",
"purpose": "academic-research",
"format": "csv",
"irbReference": "IRB-2026-AFG-014"
}
Response 202:
{ "jobId": "exp_01HX...", "status": "queued", "estimatedReadyAt": "2026-04-18T08:30:00Z" }
Errors: 403 CONSENT_REQUIRED, 422 DEIDENT_K_THRESHOLD_VIOLATION
GET /api/v1/population-health/exports/:id
Check export job status and retrieve download URL.
Response 200:
{
"id": "exp_01HX...",
"status": "completed",
"downloadUrl": "https://...",
"expiresAt": "2026-04-19T08:00:00Z",
"rowCount": 4120,
"format": "csv"
}
FHIR REST Mappings (via interop-service)
| FHIR Operation | Notes |
|---|---|
GET /fhir/MeasureReport?measure={id}&period={range} | Quality metric exchange |
GET /fhir/Measure | Supported measure definitions |
GET /fhir/Group?code=population-cohort | Published cohort populations |
POST /fhir/MeasureReport/$evaluate-measure | Trigger on-demand evaluation |
GET /fhir/Patient?_security=de-identified | De-identified patient records for research |