Facility Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: 05 API Design · API_PATH_CONVENTIONS
1. Conventions
- Base path:
/api/v1 - Authentication: Bearer JWT (issued by identity-service). Required scopes per endpoint.
- Tenant context: derived from
tidclaim; never from path/body. - Pagination:
?page={n}&limit={n}(default 20, max 200); response envelope{ data, meta: { total, page, limit } }. - Idempotency: mutation endpoints accept
Idempotency-Keyheader. - Optimistic lock:
If-Match: "<version>"on PUT/PATCH. - Errors follow
{ error: { code, message, details? } }.
2. Hierarchy Nodes
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/hierarchy/nodes | facility:write | Create node |
GET | /api/v1/hierarchy/nodes | facility:read | List with filters |
GET | /api/v1/hierarchy/nodes/{id} | facility:read | Get by id |
PATCH | /api/v1/hierarchy/nodes/{id} | facility:write | Update name/code/metadata |
POST | /api/v1/hierarchy/nodes/{id}/deactivate | facility:admin | Soft delete |
GET | /api/v1/hierarchy/nodes/{id}/subtree?maxDepth= | facility:read | Descendants |
GET | /api/v1/hierarchy/nodes/{id}/ancestors | facility:read | Root → node chain |
Request — POST /hierarchy/nodes:
{ "type": "ward", "name": "Pediatric Ward 1", "code": "PW-01", "parentNodeId": "hnd_01H...", "metadata": {"capacity": 12, "timezone": "Asia/Kabul"} }
Response 201: HierarchyNode JSON + Location: /api/v1/hierarchy/nodes/hnd_01H...
Errors: 422 HIERARCHY_NODE_TYPE_NOT_ALLOWED, 409 HIERARCHY_MULTIPLE_ROOTS, 403 ACCESS_DENIED.
3. Hierarchy Edges
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/hierarchy/edges | facility:write | Create edge |
DELETE | /api/v1/hierarchy/edges/{id} | facility:write | Remove edge |
GET | /api/v1/hierarchy/nodes/{id}/edges | facility:read | List edges for node |
Errors: 409 HIERARCHY_CYCLE, 422 EDGE_RELATIONSHIP_NOT_ALLOWED.
4. Hierarchy Profiles
| Method | Path | Auth Scope | Description |
|---|---|---|---|
GET | /api/v1/hierarchy/profiles | facility:read | List (platform + tenant) |
GET | /api/v1/hierarchy/profiles/{id} | facility:read | Get |
POST | /api/v1/hierarchy/profiles | platform:admin | Create platform profile |
POST | /api/v1/hierarchy/profiles/{id}/clone | facility:admin | Tenant clone |
PATCH | /api/v1/hierarchy/profiles/{id} | platform:admin/facility:admin | Update (tenant-owned only) |
POST | /api/v1/hierarchy/profiles/{id}/default | facility:admin | Mark default |
5. Locations
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/locations | facility:write | Create |
GET | /api/v1/locations?hierarchyNodeId=&type=&status=&serviceType=&q= | facility:read | Search |
GET | /api/v1/locations/{id} | facility:read | Get |
PUT | /api/v1/locations/{id} | facility:write | Replace |
PUT | /api/v1/locations/{id}/hours | facility:write | Set 7-day hours |
GET | /api/v1/locations/{id}/hours | facility:read | Get hours |
PUT | /api/v1/locations/{id}/status?force={bool} | facility:admin | Activate/deactivate |
Request — POST /locations:
{ "hierarchyNodeId": "hnd_01H...", "name": "Ibn Sina ER", "serviceType": "EMERGENCY", "timezone": "Asia/Kabul", "locale": "ps", "capacity": 40 }
Errors: 400 LOCATION_NODE_REQUIRED, 409 LOCATION_HAS_OCCUPIED_BEDS.
6. Beds
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/beds | facility:write | Create |
GET | /api/v1/beds?locationId=&status=&bedClass=&isolation= | facility:read | Search |
GET | /api/v1/beds/{id} | facility:read | Get |
PUT | /api/v1/beds/{id}/status | facility:write | Transition state |
POST | /api/v1/beds/{id}/housekeeping | facility:write | Update housekeeping |
Errors: 409 BED_ALREADY_OCCUPIED, 409 BED_INVALID_TRANSITION.
7. Resource Catalog
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/resource-catalog | facility:write | Create |
GET | /api/v1/resource-catalog?locationId=&type=&status= | facility:read | Search |
PATCH | /api/v1/resource-catalog/{id} | facility:write | Update |
POST | /api/v1/resource-catalog/{id}/deactivate | facility:write | Deactivate |
8. Provider Memberships
| Method | Path | Auth Scope | Description |
|---|---|---|---|
POST | /api/v1/internal/provider-memberships | internal:provider-directory | Assign (service-to-service) |
DELETE | /api/v1/internal/provider-memberships/{id} | internal:provider-directory | Revoke |
GET | /api/v1/internal/providers/{id}/context | internal:any-service | Returns active memberships + subtree — hot path (p99 ≤ 20ms) |
9. Reporting / Import / Export
| Method | Path | Auth Scope | Description |
|---|---|---|---|
GET | /api/v1/reporting/facility-dimensions | facility:read | Service-line & bed-class aggregates |
GET | /api/v1/facility-definitions/export | facility:admin | Full snapshot artefact (JSON) |
POST | /api/v1/facility-definitions/import?dryRun={bool} | facility:admin | Restore / seed |
10. FHIR Mapping
| Internal resource | FHIR R4 |
|---|---|
HierarchyNode (org types: moph/region/district/hospital/clinic) | Organization |
HierarchyNode (ward/room/department) + Location | Location (with partOf = parent Location) |
Location operational hours | Location.hoursOfOperation |
ResourceCatalogItem (type=EQUIPMENT) | Device (via interop-service projection) |
| Method | Path | Notes |
|---|---|---|
GET/POST/PUT | /fhir/R4/Location[/{id}] | Search by hierarchy-node (custom) |
GET/POST | /fhir/R4/Organization[/{id}] | |
GET/POST | /fhir/R4/HealthcareService[/{id}] | Derived from Location + ResourceCatalog |
11. Pagination / Filtering / Sorting
Sorting: ?sort=name / -name. Multi-value filters: ?status=active,inactive.
12. Error Response Example
{ "error": { "code": "HIERARCHY_CYCLE", "message": "Adding this edge would create a cycle under relationship 'contains'", "details": { "parentNodeId": "hnd_01H...", "childNodeId": "hnd_01H..." } } }
13. Rate Limits
| Endpoint class | Limit |
|---|---|
| Reads | 1000 rps / tenant |
| Writes | 200 rps / tenant |
| Internal context lookup | 5000 rps / caller service |