Skip to main content

Document Service — API Contracts

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 05 api-design


1. Auth Model

HeaderRequirement
Authorization: Bearer {JWT}All endpoints; Keycloak OIDC
Idempotency-Key: {uuid}Required on: POST generate, POST publish, POST upload finalize
X-Client-Mutation-Id: {uuid}Optional; for offline queue replay

tenantId always extracted from JWT. Never accepted from request body alone.


2. Template APIs

2.1 GET /v1/document-templates

List templates. Paginated (cursor-based).

Query Parameters:

ParameterTypeRequiredDescription
categorystringNoFilter by document category
statusstringNodraft | published | retired
facilityIdstringNoFilter by facility scope
originstringNoplatform | tenant | all (default all)
platformFormKeystringNoExact match on reference form key
cursorstringNoPagination cursor
limitnumberNoDefault 20, max 100

Response 200:

{
"items": [
{
"id": "tmpl_01JRXXXX",
"name": "General Test Requisition",
"category": "lab_requisition",
"status": "published",
"origin": "platform",
"platformFormKey": "platform.dms.general-lab.general-test-requisition",
"currentVersionId": "tv_01JRXXXX",
"updatedAt": "2026-04-18T09:00:00Z"
}
],
"nextCursor": "eyJ..."
}

2.2 POST /v1/document-templates

Create a draft template.

Auth: DOCUMENT_AUTHOR or TENANT_ADMIN (requires ehr.documents.designer license)

Request:

{
"name": "Outpatient Prescription",
"category": "prescription",
"facilityId": null
}

Response 201:

{
"id": "tmpl_01JRXXXX",
"name": "Outpatient Prescription",
"category": "prescription",
"status": "draft",
"origin": "tenant",
"createdAt": "2026-04-18T09:00:00Z"
}

2.3 GET /v1/document-templates/:templateId

Template detail including version list metadata.

Response 200: Template object with versions[] array.


2.4 PATCH /v1/document-templates/:templateId

Update draft template metadata. Cannot update published templates.

Request: { "name"?: string, "category"?: string, "facilityId"?: string }

Response 200: Updated template object.

Errors: 409 if template is not in draft status.


2.5 POST /v1/document-templates/fork

Create a tenant-owned draft from a platform reference template.

Auth: DOCUMENT_AUTHOR (requires ehr.documents.designer license)

Request:

{ "platformFormKey": "platform.dms.general-lab.general-test-requisition" }

Response 201:

{
"templateId": "tmpl_01JRXXXX",
"status": "draft",
"forkedFromPlatformFormKey": "platform.dms.general-lab.general-test-requisition"
}

2.6 POST /v1/document-templates/:templateId/versions

Create a new draft version from a definition.

Request:

{
"definition": { "sections": [], "bindings": [] },
"semver": "1.0.0"
}

Response 201: Template version object with id, semver, checksum.


2.7 POST /v1/document-templates/versions/:versionId/publish

Publish a draft version (immutable after this).

Auth: DOCUMENT_AUTHOR (requires ehr.documents.designer license)

Headers: Idempotency-Key: {uuid}

Request:

{
"effectiveFrom": "2026-04-18",
"effectiveTo": null
}

Response 200: Published version object with publishedAt.

Errors: 409 VERSION_ALREADY_PUBLISHED if version already published.


3. Generation APIs

3.1 POST /v1/documents/generate

Synchronous PDF generation (p95 < 5 s).

Headers: Idempotency-Key: {uuid}

Request:

{
"templateVersionId": "tv_01JRXXXX",
"patientId": "pat_01JRXXXX",
"encounterId": "enc_01JRXXXX",
"context": {
"resourceType": "MedicationRequest",
"id": "medreq_01JRXXXX"
},
"locale": "ps-AF",
"clientMutationId": "uuid-v4"
}

Response 200:

{
"documentReferenceId": "DocRef/01JRXXXX",
"binaryId": "Binary/01JRXXXX",
"download": {
"url": "https://storage.ghasi-health.af/presigned/...",
"expiresAt": "2026-04-18T09:15:00Z"
}
}

Errors:

CodeHTTPDescription
TEMPLATE_VERSION_NOT_PUBLISHED422Version is draft or retired
BINDING_RESOLUTION_FAILED422Required FHIR resource not found
CONTEXT_RESOURCE_NOT_FOUND422Context FHIR resource missing
MODULE_NOT_LICENSED403ehr.documents not active for tenant

3.2 POST /v1/documents/render-jobs

Enqueue asynchronous render job.

Request: Same body as POST /v1/documents/generate.

Response 202:

{ "jobId": "rjob_01JRXXXX", "status": "queued" }

3.3 GET /v1/documents/render-jobs/:jobId

Poll render job status.

Response 200:

{
"jobId": "rjob_01JRXXXX",
"status": "completed",
"documentReferenceId": "DocRef/01JRXXXX",
"binaryId": "Binary/01JRXXXX",
"durationMs": 3420
}

Status values: queued | running | completed | failed

On failure: includes errorCode and message (no PHI in message).


4. Artifact APIs

4.1 GET /v1/documents

Search documents by patient.

Query Parameters:

ParameterTypeRequiredDescription
patientIdstringYes
encounterIdstringNoFilter by encounter
categorystringNoFilter by document category
dateFromstring (ISO-8601)No
dateTostring (ISO-8601)No
cursorstringNoPagination cursor
limitnumberNoDefault 20

Response 200:

{
"items": [
{
"documentReferenceId": "DocRef/01JRXXXX",
"name": "General Test Requisition",
"category": "lab_requisition",
"date": "2026-04-18",
"author": "Dr. Bilal Wardak",
"downloadUrl": "https://..."
}
],
"nextCursor": "eyJ..."
}

4.2 GET /v1/documents/:artifactId/download

Returns a presigned download URL (default TTL 15 min) or streams bytes depending on deployment policy.

Response 200:

{
"url": "https://storage.ghasi-health.af/presigned/...",
"expiresAt": "2026-04-18T09:15:00Z",
"contentType": "application/pdf"
}

Audit record emitted on every call (action=download).


4.3 POST /v1/documents/uploads

Initiate upload. Returns staged upload URL for large files or accepts multipart body.

Request: multipart/form-data with file field, OR

{
"filename": "scan-referral-letter.pdf",
"contentType": "application/pdf",
"patientId": "pat_01JRXXXX",
"encounterId": "enc_01JRXXXX",
"category": "referral"
}

Response 201:

{
"uploadId": "upl_01JRXXXX",
"stagedUploadUrl": "https://storage.ghasi-health.af/staged/...",
"expiresAt": "2026-04-18T09:10:00Z"
}

4.4 POST /v1/documents/uploads/:uploadId/complete

Finalize upload after blob written to staged URL. Triggers virus scan.

Headers: Idempotency-Key: {uuid}

Response 201 (virus clean):

{
"documentReferenceId": "DocRef/01JRXXXX",
"status": "registered"
}

Response 422 (virus detected):

{
"error": {
"code": "VIRUS_DETECTED",
"message": "Upload failed virus scan and has been quarantined."
}
}

5. FHIR REST (via interop-service / FHIR Gateway)

Clients use fhir-gateway for standard FHIR interactions:

InteractionFHIR URL
Search documentsGET /fhir/R4/DocumentReference?patient=Patient/{id}
Read binary metadataGET /fhir/R4/Binary/{id}
Search by typeGET /fhir/R4/DocumentReference?type={loinc-code}

Direct FHIR create via POST /fhir/R4/DocumentReference is supported for rare system integrations; prefer the REST generate flow for standard use.


6. Standard Error Envelope

{
"success": false,
"error": {
"code": "BINDING_RESOLUTION_FAILED",
"message": "Human-readable message",
"details": { "bindingPath": "Patient.name" }
},
"correlationId": "req_doc_xxx",
"timestamp": "2026-04-18T09:00:00Z"
}

7. Service-Specific Error Codes

CodeHTTPDescription
MODULE_NOT_LICENSED403ehr.documents license not active for tenant
DESIGNER_LICENSE_REQUIRED403ehr.documents.designer required for authoring
TEMPLATE_NOT_FOUND404Template ID not found for tenant
TEMPLATE_VERSION_NOT_PUBLISHED422Version is draft or retired
TEMPLATE_IMMUTABLE409Attempt to edit a published version
PLATFORM_TEMPLATE_IMMUTABLE403Attempt to edit a platform reference template
BINDING_RESOLUTION_FAILED422FHIR binding could not be resolved
CONTEXT_RESOURCE_NOT_FOUND422Required context FHIR resource not found
VERSION_ALREADY_PUBLISHED409Version already in published state
CIRCULAR_SUPERSESSION409Supersession would create a circular reference chain
VIRUS_DETECTED422Upload failed virus scan; quarantined
UPLOAD_NOT_FOUND404Upload ID not found
RENDER_JOB_NOT_FOUND404Job ID not found