Ghasi e-Prescribing Gateway Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
Conventions
- Direct base path:
/v1/ghasi-e-prescribing-gateway/fhir(Kong route) - Interop proxy path:
/fhir/R4/interop/ghasi-eprescribing/*(via fhir-gateway — preferred for first-party services) - Auth:
Authorization: Bearer <JWT>— Keycloak;tenantIdfrom JWT only - Persona enforcement: JWT
personaclaim must beehr-backendfor MR writes;pharmacy-backendfor MD writes - Idempotency-Key: Required header on all POST operations (create)
- ETag: Returned on GET; required via
If-Matchon PUT/PATCH - Content-Type:
application/fhir+json - Error format: FHIR
OperationOutcomeon validation failures; JSON{ code, message }on gateway errors
Resource: MedicationRequest
POST /fhir/MedicationRequest
Create a new prescription (EHR/orders-service only).
| Field | Value |
|---|---|
| Actor | ehr-backend persona |
| Required headers | Idempotency-Key, Authorization |
Request body (FHIR R4 MedicationRequest):
{
"resourceType": "MedicationRequest",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": [{ "system": "http://www.whocc.no/atc", "code": "J01CA01", "display": "Ampicillin" }]
},
"subject": { "reference": "Patient/pat_01..." },
"requester": { "reference": "Practitioner/prac_01..." },
"authoredOn": "2026-04-18",
"dispenseRequest": { "quantity": { "value": 30, "unit": "tablet" } }
}
Response 201:
Headers: ETag: W/"a3f9..."
Location: /fhir/MedicationRequest/mr_01...
X-Prescription-Business-Id: prx_01...
X-Correlation-Id: req_01...
Body: { FHIR MedicationRequest with id, meta.versionId, meta.lastUpdated }
Errors: 403 FORBIDDEN_WRITE_PERSONA, 422 (OperationOutcome on validation), 409 (idempotency conflict), 403 MODULE_NOT_LICENSED
GET /fhir/MedicationRequest/:id
Retrieve a single MedicationRequest.
| Field | Value |
|---|---|
| Actor | ehr-backend or pharmacy-backend (tenant-scoped) |
| Response headers | ETag: W/"..." |
Response 200: FHIR MedicationRequest + ETag.
Errors: 404 (not found for tenant), 403 (wrong tenant)
GET /fhir/MedicationRequest
Search MedicationRequests.
| Query param | Description |
|---|---|
patient | Patient/<id> — required |
status | Filter by status (active, cancelled, completed) |
_count | Page size (default 20, max 100) |
_offset | Offset for pagination |
authored | Date range filter (ge2026-01-01) |
Response 200: FHIR Bundle (type=searchset) with matching resources.
PUT /fhir/MedicationRequest/:id
Update an existing MedicationRequest.
| Field | Value |
|---|---|
| Actor | ehr-backend only |
| Required headers | If-Match: W/"<current-etag>" |
Response 200: Updated FHIR MedicationRequest with new ETag.
Errors: 412 PRECONDITION_FAILED (stale ETag), 403, 422
Resource: MedicationDispense
POST /fhir/MedicationDispense
Record pharmacy fulfillment.
| Field | Value |
|---|---|
| Actor | pharmacy-backend persona only |
| Required headers | Idempotency-Key, Authorization |
Request body (FHIR R4 MedicationDispense):
{
"resourceType": "MedicationDispense",
"status": "completed",
"medicationCodeableConcept": {
"coding": [{ "system": "http://www.whocc.no/atc", "code": "J01CA01" }]
},
"subject": { "reference": "Patient/pat_01..." },
"authorizingPrescription": [{ "reference": "MedicationRequest/mr_01..." }],
"quantity": { "value": 30, "unit": "tablet" },
"whenHandedOver": "2026-04-20T14:00:00Z",
"performer": [{ "actor": { "reference": "Practitioner/pharm_01..." } }]
}
Response 201: FHIR MedicationDispense with ETag.
Errors: 422 PRESCRIPTION_NOT_FOUND (MR not known to tenant), 403 FORBIDDEN_WRITE_PERSONA, 422 (profile validation)
GET /fhir/MedicationDispense/:id
Retrieve a single MedicationDispense.
Response 200: FHIR MedicationDispense + ETag.
GET /fhir/MedicationDispense
Search by prescription reference.
| Query param | Description |
|---|---|
request | MedicationRequest/<id> — filter dispenses for a prescription |
patient | Patient/<id> |
status | completed, in-progress |
Resource: Subscription
POST /fhir/Subscription
Register an HTTPS notification channel.
| Field | Value |
|---|---|
| Actor | ehr-backend or pharmacy-backend |
Request body:
{
"resourceType": "Subscription",
"status": "requested",
"criteria": "MedicationDispense?authorizingPrescription=MedicationRequest/mr_01...",
"channel": {
"type": "rest-hook",
"endpoint": "https://pharmacy.example.com/webhooks/eprescribing",
"payload": "application/fhir+json",
"header": ["X-Api-Key: <secret>"]
}
}
Response 201: FHIR Subscription with status: active after endpoint verification.
GET /fhir/Subscription/:id
Get Subscription status and cursor position.
DELETE /fhir/Subscription/:id
Unregister a notification channel.
Resource: Task (P1 — Renewals and Clarifications)
POST /fhir/Task
Create a renewal or clarification task.
| Field | Value |
|---|---|
| Actor | pharmacy-backend (requester) or ehr-backend (policy) |
Response 201: FHIR Task with status requested.
PATCH /fhir/Task/:id
Update Task status (accept, reject, complete).
Resource: Organization / Endpoint (Directory, P1)
GET /fhir/Organization
Search for pharmacy organizations.
| Query param | Description |
|---|---|
name | Partial name search |
type | Organization type (pharmacy) |
_count | Page size |
FHIR Operations
POST /fhir/MedicationRequest/$validate
Validate a MedicationRequest against the tenant's pinned IG without persisting.
Response 200: OperationOutcome with informational issues (valid) or error issues (invalid).
Common Error Codes
| HTTP | Code | Meaning |
|---|---|---|
| 403 | FORBIDDEN_WRITE_PERSONA | Wrong actor type for this resource |
| 403 | MODULE_NOT_LICENSED | Tenant lacks gateway entitlement |
| 403 | TENANT_ISOLATION_VIOLATION | Cross-tenant read/write attempt |
| 409 | IDEMPOTENCY_KEY_CONFLICT | Same key, different payload |
| 412 | PRECONDITION_FAILED | Stale ETag on update |
| 422 | PROFILE_VALIDATION_FAILURE | IG profile constraint violated (OperationOutcome body) |
| 422 | PRESCRIPTION_NOT_FOUND | MD references MR unknown to tenant |
| 429 | RATE_LIMIT_EXCEEDED | Per-tenant rate limit hit |
| 503 | VALIDATOR_UNAVAILABLE | IG validator service unavailable (degraded mode) |