Medication Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · API paths · FHIR-first
1. Conventions
- Base path (internal REST):
/api/v1/medications,/api/v1/dispenses,/api/v1/stock-items,/api/v1/mar,/api/v1/reconciliation. - Base path (FHIR R4): via
fhir-gateway— this service ownsMedicationRequest(chart side),MedicationAdministration,MedicationStatement,Medication,MedicationKnowledge. National interop copies of MR/MD live in ghasi-eprescribing-gateway-service. - AuthN: Bearer JWT (identity-service).
- AuthZ: scopes (see SECURITY_MODEL).
- Idempotency:
Idempotency-Keyheader required forPOST /dispensesandPOST /medications/{id}/sign. - Concurrency:
If-MatchETag on PUT/PATCH. - Pagination:
?page=1&pageSize=50, max 200; cursor style for FHIR. - Response envelope:
{ success, data, error, meta }.
2. Prescriptions (Medication list & prescribing)
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/medications | medication:prescribe | Draft a prescription |
| GET | /api/v1/medications | medication:read | List for a patient (patientId, status, source) |
| GET | /api/v1/medications/{id} | medication:read | Retrieve single |
| PATCH | /api/v1/medications/{id} | medication:prescribe | Update draft (pre-sign) |
| POST | /api/v1/medications/{id}/sign | medication:prescribe | Sign — runs safety checks; includes overrides[] if required |
| POST | /api/v1/medications/{id}/override | medication:prescribe | Record override standalone |
| PUT | /api/v1/medications/{id}/discontinue | medication:prescribe | Discontinue with reason |
| PUT | /api/v1/medications/{id}/hold | medication:prescribe | Place on hold |
| PUT | /api/v1/medications/{id}/resume | medication:prescribe | Resume from hold |
| POST | /api/v1/medications/{id}/refill | medication:prescribe | Authorize refill |
Sign request schema
{
"acknowledgeAlerts": [
{ "alertId": "...", "overrideReason": "patient has tolerated amox historically, allergy re-assessed" }
],
"destination": "pharmacy:PHX-001",
"preferredPharmacyId": "phm_...",
"transmitExternally": false
}
Error codes
| Code | Meaning |
|---|---|
MED_ALERT_BLOCKING (422) | Blocking alert without override |
MED_CROSS_TENANT (403) | Cross-tenant reference |
MED_PRECONDITION_FAILED (412) | Stale ETag |
MED_INVALID_STATUS (409) | Status transition not allowed |
MED_DRUG_KB_UNAVAILABLE (503) | KB check failed |
3. Dispensing
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/dispenses | dispense:create | Record a dispense; atomic inventory decrement |
| GET | /api/v1/dispenses | dispense:read | Queue with filters |
| GET | /api/v1/dispenses/{id} | dispense:read | Single dispense |
| POST | /api/v1/dispenses/{id}/return | dispense:create | Return/undispense |
| POST | /api/v1/dispenses/{id}/countersign | dispense:countersign | Schedule II counter-sign |
Dispense request
{
"prescriptionId": "rx_...",
"stockItemId": "stk_...",
"dispensedQuantity": 30,
"quantityUnit": "tablet",
"lotNumber": "LOT-2026-04",
"expiryDate": "2027-12-31",
"partial": false,
"notes": "labeled"
}
Idempotency: server computes fingerprint on (prescriptionId, dispensedQuantity, lotNumber, idempotencyKey).
4. Stock / Inventory
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/stock-items | inventory:write | Receive stock (GRN) |
| GET | /api/v1/stock-items | inventory:read | List stock with filters |
| PATCH | /api/v1/stock-items/{id} | inventory:write | Adjust (threshold-gated) |
| POST | /api/v1/stock-items/{id}/transfer | inventory:transfer | Transfer between nodes |
| POST | /api/v1/stock-items/{id}/recall | inventory:recall | Mark lot recalled |
| GET | /api/v1/stock-items/reorder-alerts | inventory:read | Below reorder point |
| GET | /api/v1/stock-items/expiry-alerts | inventory:read | Within expiry horizon |
5. Medication Administration (MAR)
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/mar | mar:record | Record administration |
| GET | /api/v1/mar | mar:read | Per patient or per prescription |
| PATCH | /api/v1/mar/{id}/correct | mar:record | Correction (new entry linked) |
6. Reconciliation
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/reconciliation | reconciliation:start | Start session |
| GET | /api/v1/reconciliation/{id} | reconciliation:read | Session + diff |
| PATCH | /api/v1/reconciliation/{id}/items/{lineId} | reconciliation:action | Continue / Discontinue / Modify |
| POST | /api/v1/reconciliation/{id}/complete | reconciliation:complete | Finalize |
7. FHIR REST (via fhir-gateway)
| Resource | Interactions | Search params |
|---|---|---|
MedicationRequest | read, search, create (chart), update | subject, status, category, _lastUpdated |
MedicationAdministration | read, search, create | subject, medication, effective-time |
MedicationStatement | read, search, create | subject, status |
Medication | read, search | code |
MedicationKnowledge | read, search | code, ingredient |
DetectedIssue | read, search | patient, category (alert-override records) |
8. Rate Limits
| Endpoint | Limit |
|---|---|
POST /medications/{id}/sign | 30 rpm per actor |
POST /dispenses | 120 rpm per actor |
GET /dispenses (queue) | 300 rpm per actor |
9. Errors (common envelope)
{
"success": false,
"error": {
"code": "MED_ALERT_BLOCKING",
"message": "Signing blocked by 1 critical alert",
"details": { "alerts": [ { "id": "...", "severity": "high", "type": "drug-allergy" } ] }
}
}