Laboratory Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
1. Base Path and Auth
| Attribute | Value |
|---|---|
| REST base path | /v1/laboratory |
| FHIR base path | /fhir/R4 (via interop-service gateway) |
| Auth | Bearer JWT (Keycloak); tenantId resolved from JWT realm |
| Module guard | ModuleEntitlementGuard — requires diag.laboratory license |
| Idempotency | Idempotency-Key header required on all POST mutations |
2. Test Catalog
GET /v1/laboratory/catalog
Search test catalog items.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:read |
| Query params | q (text search), system (loinc), specimenType, page, pageSize |
Response 200:
{
"data": [{
"id": "cat_01J...",
"name": "Potassium",
"loincCode": "2823-3",
"specimenType": "serum",
"panel": false,
"referenceRanges": [{ "low": 3.5, "high": 5.1, "unit": "mmol/L" }],
"tenantId": null
}],
"meta": { "total": 120, "page": 1, "pageSize": 20 }
}
POST /v1/laboratory/catalog
Create test catalog item.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:admin |
Request body:
{
"name": "Hemoglobin A1c",
"loincCode": "4548-4",
"specimenType": "whole-blood",
"referenceRanges": [{ "high": 5.7, "unit": "%" }]
}
Response 201: Created catalog item object.
Errors:
| Code | HTTP | Meaning |
|---|---|---|
LAB_CATALOG_DUPLICATE | 409 | LOINC code already exists for tenant |
LAB_LOINC_INVALID | 422 | Code not found in terminology-service |
3. Accessions
POST /v1/laboratory/accessions
Create a new accession from a lab order.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:write |
| Idempotency-Key | Required |
Request body:
{
"orderId": "SRQ_01J...",
"patientId": "PAT_01J...",
"encounterId": "ENC_01J...",
"priority": "routine",
"tests": ["2823-3", "4548-4"]
}
Response 201:
{
"id": "ACC_01J...",
"accessionNumber": "AFG-20260418-000123",
"status": "ordered",
"priority": "routine",
"patientId": "PAT_01J...",
"createdAt": "2026-04-18T08:00:00Z"
}
GET /v1/laboratory/accessions
List accessions — worklist view.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:read |
| Query params | status, priority, bench, from, to, page, pageSize |
Response 200: Paginated accession list.
GET /v1/laboratory/accessions/:id
Get full accession detail including specimens and results.
Response 200:
{
"id": "ACC_01J...",
"accessionNumber": "AFG-20260418-000123",
"status": "verified",
"specimens": [...],
"results": [...]
}
POST /v1/laboratory/accessions/:id/cancel
Cancel accession.
Request body: { "reason": "Patient refused" }
Errors:
| Code | HTTP | Meaning |
|---|---|---|
LAB_ACCESSION_ALREADY_RELEASED | 409 | Cannot cancel released accession |
4. Specimens
POST /v1/laboratory/accessions/:accessionId/specimens
Record specimen collection.
Request body:
{
"type": "serum",
"collectedAt": "2026-04-18T07:45:00Z",
"collectorId": "PRV_01J...",
"condition": "satisfactory",
"volume": "5 mL"
}
Response 201: Specimen object.
POST /v1/laboratory/specimens/:id/receive
Mark specimen received at lab.
Request body: { "receivedAt": "2026-04-18T08:10:00Z" }
5. Results
POST /v1/laboratory/accessions/:accessionId/results
Enter a result for a test.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:write |
Request body:
{
"testCode": "2823-3",
"value": { "quantity": 3.2, "unit": "mmol/L" },
"referenceRange": { "low": 3.5, "high": 5.1 },
"abnormalFlag": "L",
"comments": "Slightly low"
}
Response 201: Result object with criticalFlag assessed.
POST /v1/laboratory/results/:id/verify
Technologist verification.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:verify |
Response 200: Updated result with status: verified.
POST /v1/laboratory/accessions/:accessionId/release
Release all verified results to chart.
| Attribute | Value |
|---|---|
| Auth scope | svc:laboratory:release |
Response 200: { "releasedCount": 4, "fhirDiagnosticReportId": "DR_01J..." }
Errors:
| Code | HTTP | Meaning |
|---|---|---|
LAB_NO_VERIFIED_RESULTS | 422 | No verified results to release |
LAB_CRITICAL_ACK_REQUIRED | 422 | Critical result not acknowledged |
POST /v1/laboratory/results/:id/correct
Submit a correction.
Request body:
{
"correctionReason": "Transcription error",
"value": { "quantity": 4.1, "unit": "mmol/L" }
}
Response 201: New result version linked to prior.
GET /v1/laboratory/results/queue
Clinician result acknowledgment queue.
| Query params | status (unreviewed, critical), assigneeId, page, pageSize |
6. Acknowledgments
POST /v1/laboratory/results/:id/acknowledge
Request body: { "ackType": "reviewed", "comments": "Noted, will adjust medication" }
Response 200: { "ackId": "ACK_01J...", "ackBy": "USR_01J...", "ackAt": "..." }
7. FHIR Endpoints (via interop-service gateway)
| Interaction | Path | Notes |
|---|---|---|
| Search Observations | GET /fhir/R4/Observation?patient={id}&category=laboratory | Routed to laboratory-service |
| Read Observation | GET /fhir/R4/Observation/{id} | |
| Search DiagnosticReport | GET /fhir/R4/DiagnosticReport?patient={id}&category=LAB | |
| Read DiagnosticReport | GET /fhir/R4/DiagnosticReport/{id} | |
| Search Specimen | GET /fhir/R4/Specimen?patient={id} | |
| Read Specimen | GET /fhir/R4/Specimen/{id} |
FHIR error format: OperationOutcome with issue[].code and issue[].diagnostics.
8. Common Error Codes
| Code | HTTP | Description |
|---|---|---|
LAB_ACCESSION_NOT_FOUND | 404 | Accession ID does not exist |
LAB_RESULT_NOT_FOUND | 404 | Result ID does not exist |
LAB_INVALID_STATE_TRANSITION | 409 | State machine violation |
LAB_TENANT_MISMATCH | 403 | Resource belongs to different tenant |
LAB_MODULE_NOT_LICENSED | 403 | diag.laboratory not licensed for tenant |
VALIDATION_FAILED | 400 | DTO validation error with field details |
9. Pagination
All list endpoints follow standard envelope:
{
"data": [...],
"meta": { "total": 250, "page": 2, "pageSize": 20 }
}