Skip to main content

Population Health Service — API Contracts

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD

Global Conventions

ConventionValue
Base path (Kong)/api/v1/population-health
AuthAuthorization: Bearer <Keycloak RS256 JWT>
Content-Typeapplication/json
Paginationpage (1-based), pageSize (max 100, default 20)
Tenant scopingExtracted from JWT tenantId claim; cross-tenant = 403
ID formatULID with service prefix (e.g., coh_, reg_, ors_)
TimestampsISO-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

HTTPCodeMeaning
400INVALID_COHORT_DSLCohort expression tree failed schema validation
400INVALID_QUERY_PARAMSMalformed query parameters
401UNAUTHORIZEDMissing or invalid JWT
403ACCESS_DENIEDInsufficient role/permission
403CONSENT_REQUIREDSecondary-use consent not recorded
403CROSS_TENANT_SCOPE_VIOLATIONRequested scope outside authorized tenant
403MODULE_NOT_LICENSEDTenant does not have population-health licensed
404COHORT_NOT_FOUNDCohort ID not found
404RESOURCE_NOT_FOUNDGeneric resource not found
409REFRESH_JOB_ACTIVECohort refresh already running
409EXPORT_JOB_ACTIVEDuplicate export job for same period
422DEIDENT_K_THRESHOLD_VIOLATIONk-anonymity threshold not met; export suppressed
422INVALID_OUTREACH_TRANSITIONFSM state transition not allowed
429EXPORT_RATE_LIMITExport requests exceed tenant quota
503DHIS2_UNAVAILABLEHMIS 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:

ParamTypeRequiredDescription
fromISO-8601NoLower bound for encounter/event window
toISO-8601NoUpper bound
facilityIdULIDNoFilter to facility
departmentIdULIDNoFilter to department
providerIdULIDNoFilter to provider
nodeIdULIDNoNode 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:

ParamTypeDescription
riskTierlow|medium|high|criticalFilter by risk tier
controlStatusstringe.g., controlled, uncontrolled
facilityIdULIDFacility scope
providerIdULIDProvider scope
page, pageSizeintPagination

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 OperationNotes
GET /fhir/MeasureReport?measure={id}&period={range}Quality metric exchange
GET /fhir/MeasureSupported measure definitions
GET /fhir/Group?code=population-cohortPublished cohort populations
POST /fhir/MeasureReport/$evaluate-measureTrigger on-demand evaluation
GET /fhir/Patient?_security=de-identifiedDe-identified patient records for research