Huli FHIR Core Implementation Guide (R4)
0.1.0 - Release

Huli FHIR Core Implementation Guide (R4) - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions

DocumentReference Mapping

DocumentReference Mapping: Huli -> FHIR R4

HuliDocumentReference points at an uploaded patient document — a lab result, imaging study, or scanned file (ehr_document_reference). The Public API supports read, search, create, and a dedicated $upload operation; a metadata PUT (and the entered-in-error soft-delete) round out the surface. The binary itself is served via a short-lived signed URL, never inlined on read.

This resource is a thin FHIR layer over the same practice document service the in-app document manager uses, so every write inherits the application's upload rules: the 25 MB ceiling, magic-byte content validation, SHA-256 dedup, and organization scoping.

Fail-closed when storage is unconfigured. When the server has no document storage (GCS bucket) wired, every DocumentReference route returns a 503 Service Unavailable OperationOutcome rather than partially serving.

Authorization

Every operation requires the SMART scope system/DocumentReference.rs (read / search) or system/DocumentReference.cru (create / read / update / $upload) AND the corresponding practice document permission on the api key's user. A valid scope on a key whose user lacks the permission is denied with 403.

DocumentReference is clinical-sensitive and therefore BAA-gated: an api key without an executed Business Associate Agreement cannot obtain these scopes (it is part of the "Medical records" scope card, alongside Composition). See Data handling and compliance for the integrator BAA obligations.

Field mapping

Huli field (ehr_document_reference) FHIR path Cardinality Must Support Notes
id_document DocumentReference.id 1..1 UUID, allocated server-side; client-supplied IDs are rejected
modified_on DocumentReference.meta.lastUpdated / meta.versionId 0..1 versionId derived from modified_on (no conditional update on this surface)
DocumentReference.meta.profile[0] 0..1 Always https://fhir.huli.ai/r4/StructureDefinition/HuliDocumentReference
status + deleted_on DocumentReference.status 1..1 Yes current while active (status = 1); entered-in-error once soft-deleted
id_patient DocumentReference.subject 1..1 Yes Reference(HuliPatient); required on read and write
id_practitioner DocumentReference.author[0] 0..* Yes Reference(HuliPractitioner); the practitioner the document is routed to for review — set on write (inline author[0] / multipart reviewer), defaults to the organization owner
id_encounter DocumentReference.context.encounter[0] 0..1 Reference(HuliEncounter); the clinical encounter the document is bound to
category DocumentReference.category[0].coding[0] 0..* system = https://fhir.huli.ai/r4/CodeSystem/document-category, code = the category code
document_type_code / _display DocumentReference.type 0..1 LOINC-coded document type (system = http://loinc.org)
description DocumentReference.description 0..1 Free-text description
created_on DocumentReference.date 0..1 When the document record was created
content_type content[0].attachment.contentType 1..1 Yes The server-detected MIME type (see Content validation)
storage_path content[0].attachment.url 0..1 Yes A 30-minute signed GCS URL (read only); never inlined; absent on search rows
file_size content[0].attachment.size 0..1 Byte size
file_hash content[0].attachment.hash 0..1 SHA-256 digest; stored as hex, emitted base64-encoded per FHIR
filename content[0].attachment.title 0..1 Original filename
document_date content[0].attachment.creation 0..1 The clinical date of the document content (YYYY-MM-DD), when recorded

Huli stores exactly one binary per document, so content is 1..1 and content[0].attachment is always present.

Status

FHIR DocumentReference.status Huli concept Notes
current active row (status = 1) Default for an uploaded document
entered-in-error soft-deleted (deleted_on set) Reached via PUT status=entered-in-error

status is required on write. superseded has no Huli equivalent (documents are not versioned on this surface) and is rejected 422 with a value issue at DocumentReference.status — only current and entered-in-error are honored.

A voided (entered-in-error) document stays retrievable as a tombstone: a GET by id returns 200 with status=entered-in-error and the stable metadata, but no content.attachment.url — the binary is gone, so no download link is served. The tombstone also appears in Patient/$everything (consistent with a voided Composition). Search (GET /DocumentReference?…) is active-document discovery and does not return voided documents — reach them by id or via $everything.

Creating a document

A document binary can be created two ways. Both route through the practice document service's Upload, inheriting every upload guard below.

POST /fhir/R4/DocumentReference (inline base64)

The body is a DocumentReference carrying the binary inline as content[0].attachment.data (base64). Required: subject (Patient reference), content[0].attachment.data, and content[0].attachment.title (the filename — its extension must match the detected content type). Optional: context.encounter[0] and author[0] (the reviewer — see Review assignment).

{
  "resourceType": "DocumentReference",
  "status": "current",
  "subject": { "reference": "Patient/01936b7c-8d4e-7000-8000-000000004001" },
  "author": [{ "reference": "Practitioner/01936b7c-8d4e-7000-8000-000000003001" }],
  "context": { "encounter": [{ "reference": "Encounter/01936b7c-8d4e-7000-8000-000000005001" }] },
  "content": [
    {
      "attachment": {
        "contentType": "application/pdf",
        "title": "resultado-laboratorio.pdf",
        "data": "JVBERi0xLjQKJ..."
      }
    }
  ]
}

POST /fhir/R4/DocumentReference/$upload

The $upload operation accepts two modes:

  1. multipart/form-data — a file part (the raw binary) plus a subject form field (a Patient reference or bare UUID) and optional encounter, category, and reviewer fields. This is the preferred path for large binaries — the stream is bounded as it is read.
  2. JSON — identical to POST /DocumentReference above (content[0].attachment.data base64).

A successful create returns 201 Created with a Location header and the rendered DocumentReference (the response carries a freshly minted 30-minute signed content[0].attachment.url).

Review assignment

Every uploaded document is routed to one practitioner's review queue. Name that practitioner with author[0] (inline mode) or the reviewer form field (multipart) — a Practitioner reference or bare UUID that must resolve to an active member of the caller's organization (an unknown or non-member reviewer is rejected 400). When none is supplied the document defaults to the organization's owner.

Size ceiling

The binary may not exceed 25 MB. An oversized upload is rejected 413 (HPB-00119) — the inline base64 path reads under a ceiling sized for the ~4/3 base64 inflation plus a JSON-envelope margin, and the multipart path bounds the stream as it is read so an oversized body is never fully buffered first.

Content validation

The content type is detected from magic bytes — the client-declared attachment.contentType is informational and does not gate the upload. The allowed types are PDF, JPEG, PNG, WebP, and DICOM; anything else is rejected 400 (HPB-00120). The supplied filename's extension must match the detected type (e.g. a PDF magic header with a .png filename is rejected 400). The stored content[0].attachment.contentType reflects the server-detected type, not the client's claim.

Deduplication

The service computes a SHA-256 of the bytes and checks for a byte-identical existing document in the organization. A duplicate is flagged (recorded in the document's metadata), not rejected — the upload still succeeds and returns a new DocumentReference.

Updating a document

PUT /fhir/R4/DocumentReference/{id} mutates metadata only — the binary content is immutable on this surface:

Body shape Effect
status = current + category / description Updates the document category and/or description (UpdateDocumentMetadata)
status = entered-in-error Soft-deletes the document (DeleteDocument); the response renders the now entered-in-error resource

There is no hard DELETE verb — soft-delete is reached exclusively through PUT status=entered-in-error. A binary re-upload is a new $upload, never a content PUT.

Search parameters

GET /fhir/R4/DocumentReference requires the patient parameter (the practice document store exposes only patient-scoped reads):

Parameter Type Notes
patient reference Required. Reference(HuliPatient); logical id or full reference
category token The document-category code; pushed down to the store (exact total)
type token The LOINC document-type code; best-effort filter over the current page
date date The document creation date; best-effort filter over the current page
_count number Page size
_cursor string Opaque continuation cursor from the previous page's next link

v1 search limitation. category is pushed down to the store and paginates with an exact Bundle.total. type and date are applied as best-effort filters over the already-fetched page — they do not repaginate, so a page may return fewer than _count matches even when more exist further down. Search rows carry no signed URL; fetch the individual DocumentReference/{id} to obtain the binary's download URL.

Search responses are returned as a FHIR Bundle of type searchset.

No history, no inlined binary

  • No _history / vread. The DocumentReference exposes only its current state.
  • The binary is never inlined on read. content[0].attachment.data is empty on read; the bytes are reachable only through the 30-minute signed content[0].attachment.url, which the client must use before it expires.

Errors

Code HTTP When
HPB-00118 404 DocumentReference not found in the authenticated organization
HPB-00119 413 Upload exceeds the 25 MB ceiling
HPB-00120 400 Content type not allowed, or the declared/extension type does not match the detected bytes

A 503 Service Unavailable is returned when document storage is unconfigured on the server. All errors are returned as a FHIR OperationOutcome.