Skip to main content

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

AttributeValue
REST base path/v1/laboratory
FHIR base path/fhir/R4 (via interop-service gateway)
AuthBearer JWT (Keycloak); tenantId resolved from JWT realm
Module guardModuleEntitlementGuard — requires diag.laboratory license
IdempotencyIdempotency-Key header required on all POST mutations

2. Test Catalog

GET /v1/laboratory/catalog

Search test catalog items.

AttributeValue
Auth scopesvc:laboratory:read
Query paramsq (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.

AttributeValue
Auth scopesvc: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:

CodeHTTPMeaning
LAB_CATALOG_DUPLICATE409LOINC code already exists for tenant
LAB_LOINC_INVALID422Code not found in terminology-service

3. Accessions

POST /v1/laboratory/accessions

Create a new accession from a lab order.

AttributeValue
Auth scopesvc:laboratory:write
Idempotency-KeyRequired

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.

AttributeValue
Auth scopesvc:laboratory:read
Query paramsstatus, 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:

CodeHTTPMeaning
LAB_ACCESSION_ALREADY_RELEASED409Cannot 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.

AttributeValue
Auth scopesvc: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.

AttributeValue
Auth scopesvc:laboratory:verify

Response 200: Updated result with status: verified.

POST /v1/laboratory/accessions/:accessionId/release

Release all verified results to chart.

AttributeValue
Auth scopesvc:laboratory:release

Response 200: { "releasedCount": 4, "fhirDiagnosticReportId": "DR_01J..." }

Errors:

CodeHTTPMeaning
LAB_NO_VERIFIED_RESULTS422No verified results to release
LAB_CRITICAL_ACK_REQUIRED422Critical 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)

InteractionPathNotes
Search ObservationsGET /fhir/R4/Observation?patient={id}&category=laboratoryRouted to laboratory-service
Read ObservationGET /fhir/R4/Observation/{id}
Search DiagnosticReportGET /fhir/R4/DiagnosticReport?patient={id}&category=LAB
Read DiagnosticReportGET /fhir/R4/DiagnosticReport/{id}
Search SpecimenGET /fhir/R4/Specimen?patient={id}
Read SpecimenGET /fhir/R4/Specimen/{id}

FHIR error format: OperationOutcome with issue[].code and issue[].diagnostics.


8. Common Error Codes

CodeHTTPDescription
LAB_ACCESSION_NOT_FOUND404Accession ID does not exist
LAB_RESULT_NOT_FOUND404Result ID does not exist
LAB_INVALID_STATE_TRANSITION409State machine violation
LAB_TENANT_MISMATCH403Resource belongs to different tenant
LAB_MODULE_NOT_LICENSED403diag.laboratory not licensed for tenant
VALIDATION_FAILED400DTO validation error with field details

9. Pagination

All list endpoints follow standard envelope:

{
"data": [...],
"meta": { "total": 250, "page": 2, "pageSize": 20 }
}