Immunizations Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template
1. Base URL
/api/v1 (internal Kong routing); FHIR base: /fhir/R4
All requests require:
Authorization: Bearer <JWT>(Keycloak)X-Tenant-ID: <tenantId>- Module entitlement:
clinical.immunizations
2. Immunizations
POST /v1/immunizations
Record a vaccine administration.
Roles: CLINICIAN, NURSE, VACCINATION_OFFICER
Request body:
{
"patientId": "pat_01J...",
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "120", "display": "DTaP-Hib-IPV" },
"status": "completed",
"doseNumber": 1,
"seriesDoses": 3,
"lotNumber": "LOT-2024-0041",
"expiryDate": "2025-06-01",
"administeredAt": "2026-04-15T08:30:00Z",
"site": { "system": "http://snomed.info/sct", "code": "368209003", "display": "Right arm" },
"route": { "system": "http://snomed.info/sct", "code": "78421000", "display": "Intramuscular" },
"doseQuantity": { "value": 0.5, "unit": "mL" },
"performerId": "usr_01J...",
"facilityId": "fac_01J...",
"encounterId": "enc_01J...",
"notes": "Patient tolerated well",
"clientMutationId": "uuid-for-idempotency"
}
Response 201:
{
"immunizationId": "imm_01J...",
"patientId": "pat_01J...",
"status": "completed",
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "120", "display": "DTaP-Hib-IPV" },
"doseNumber": 1,
"administeredAt": "2026-04-15T08:30:00Z",
"version": 1,
"createdAt": "2026-04-15T08:32:10Z"
}
Error codes:
| Code | HTTP | Condition |
|---|---|---|
PATIENT_NOT_FOUND | 404 | patientId not found |
PATIENT_DECEASED | 422 | Patient vital status is deceased |
INVALID_VACCINE_CODE | 422 | vaccineCode not in EPI vocabulary |
CONTRAINDICATION_ACTIVE | 422 | Active contraindication exists |
DUPLICATE_MUTATION | 409 | clientMutationId already processed |
POST /v1/immunizations/refusal
Record a patient refusal.
Roles: CLINICIAN, NURSE, VACCINATION_OFFICER
Request body:
{
"patientId": "pat_01J...",
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "120", "display": "DTaP-Hib-IPV" },
"status": "not-done",
"statusReason": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActReason", "code": "PATOBJ", "display": "Patient objection" },
"recordedAt": "2026-04-15T08:30:00Z",
"performerId": "usr_01J...",
"facilityId": "fac_01J..."
}
Response 201: Same shape as POST /v1/immunizations, status not-done.
GET /v1/immunizations/:id
Get a single immunization record.
Roles: CLINICIAN, NURSE, VACCINATION_OFFICER, PATIENT (own records only)
Response 200:
{
"immunizationId": "imm_01J...",
"patientId": "pat_01J...",
"status": "completed",
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "120", "display": "DTaP-Hib-IPV" },
"doseNumber": 1,
"seriesDoses": 3,
"lotNumber": "LOT-2024-0041",
"administeredAt": "2026-04-15T08:30:00Z",
"site": { "system": "http://snomed.info/sct", "code": "368209003", "display": "Right arm" },
"route": { "system": "http://snomed.info/sct", "code": "78421000", "display": "Intramuscular" },
"doseQuantity": { "value": 0.5, "unit": "mL" },
"performerId": "usr_01J...",
"facilityId": "fac_01J...",
"encounterId": "enc_01J...",
"version": 1,
"createdAt": "2026-04-15T08:32:10Z",
"updatedAt": "2026-04-15T08:32:10Z"
}
GET /v1/immunizations
List immunization records. Supports filtering.
Roles: CLINICIAN, NURSE, VACCINATION_OFFICER, PATIENT (own)
Query params: patientId (required for non-admin), status, vaccineCode, from, to, facilityId, page, pageSize
Response 200:
{
"items": [ /* ImmunizationRecord[] */ ],
"total": 12,
"page": 1,
"pageSize": 20
}
PUT /v1/immunizations/:id
Amend an existing record (lot, site, route, notes only).
Roles: CLINICIAN, VACCINATION_OFFICER
Request body:
{
"lotNumber": "LOT-2024-0042",
"notes": "Corrected lot number",
"version": 1
}
Response 200: Updated ImmunizationRecord.
PUT /v1/immunizations/:id/correction
Mark a record as entered-in-error.
Roles: CLINICIAN, ADMIN
Request body:
{
"reason": "Recorded against wrong patient",
"version": 1
}
Response 200: { "immunizationId": "...", "status": "entered-in-error" }
3. Forecast
GET /v1/immunizations/forecast/:patientId
Get the current immunization forecast (ImmunizationRecommendation).
Roles: CLINICIAN, NURSE, VACCINATION_OFFICER, PATIENT (own)
Response 200:
{
"forecastId": "fcst_01J...",
"patientId": "pat_01J...",
"calculatedAt": "2026-04-15T09:00:00Z",
"recommendations": [
{
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "140", "display": "Influenza" },
"doseNumber": 2,
"seriesDoses": 2,
"forecastStatus": "due",
"dueDate": "2026-05-15",
"earliestDate": "2026-04-30",
"latestDate": "2026-06-30",
"series": "Influenza annual"
}
]
}
POST /v1/immunizations/forecast/:patientId/refresh
Trigger an immediate forecast recalculation.
Roles: CLINICIAN, ADMIN
Response 202: { "message": "Forecast refresh enqueued" }
4. Contraindications
POST /v1/immunizations/contraindications
Add a contraindication for a patient-antigen pair.
Roles: CLINICIAN
Request body:
{
"patientId": "pat_01J...",
"vaccineCode": { "system": "http://hl7.org/fhir/sid/cvx", "code": "03", "display": "MMR" },
"reason": { "system": "http://snomed.info/sct", "code": "416098002", "display": "Allergy to egg protein" },
"recordedAt": "2026-04-15T08:30:00Z",
"performerId": "usr_01J..."
}
Response 201: { "contraindicationId": "cind_01J...", ... }
DELETE /v1/immunizations/contraindications/:id
Remove (inactivate) a contraindication.
Roles: CLINICIAN
Response 204
5. Defaulters
GET /v1/immunizations/defaulters
List patients with overdue doses.
Roles: VACCINATION_OFFICER, ADMIN
Query params: facilityId, vaccineCode, daysOverdue (min), page, pageSize
Response 200:
{
"items": [
{
"patientId": "pat_01J...",
"patientName": "...",
"vaccineCode": { "code": "120", "display": "DTaP-Hib-IPV" },
"doseNumber": 2,
"dueDate": "2026-02-01",
"daysOverdue": 75
}
],
"total": 34,
"page": 1,
"pageSize": 20
}
6. Coverage Reporting
GET /v1/immunizations/coverage
Get population coverage statistics.
Roles: ADMIN, ANALYST
Query params: facilityId, vaccineCode, from, to, ageGroup
Response 200:
{
"vaccineCode": { "code": "120", "display": "DTaP-Hib-IPV" },
"doseNumber": 1,
"eligiblePopulation": 850,
"vaccinatedCount": 702,
"coveragePercent": 82.6,
"facilityId": "fac_01J...",
"period": { "from": "2026-01-01", "to": "2026-03-31" }
}
7. Digital Certificates
GET /v1/immunizations/certificate/:patientId
Generate a signed vaccination certificate for the patient.
Roles: CLINICIAN, PATIENT (own)
Response 200:
{
"patientId": "pat_01J...",
"issuedAt": "2026-04-18T10:00:00Z",
"issuer": "Ghasi eHealth Platform",
"certificate": "<signed JWT>",
"qrData": "<base64-encoded QR payload>"
}
8. FHIR R4 Surface
| FHIR resource | Operation | Path |
|---|---|---|
| Immunization | read | GET /fhir/R4/Immunization/:id |
| Immunization | search | GET /fhir/R4/Immunization?patient=:id&status=&date= |
| ImmunizationRecommendation | read | GET /fhir/R4/ImmunizationRecommendation/:id |
| ImmunizationRecommendation | search | GET /fhir/R4/ImmunizationRecommendation?patient=:id |
| ImmunizationEvaluation | read | GET /fhir/R4/ImmunizationEvaluation/:id |
| ImmunizationEvaluation | search | GET /fhir/R4/ImmunizationEvaluation?patient=:id |
All FHIR responses use Content-Type: application/fhir+json.
9. Historical Import
POST /v1/immunizations/historical
Import a historical immunization record.
Roles: CLINICIAN, DATA_MIGRATION
Request body: Same shape as POST /v1/immunizations with additional source field ({ "type": "paper_card" | "external_emr" | "national_registry", "reference": "..." }).
Response 201: ImmunizationRecord with status: "completed" and isHistorical: true.