01Which FHIR, and where
FHIR (Fast Healthcare Interoperability Resources) is HL7’s standard for
health data: a catalogue of resources such as Patient,
Observation and MedicationRequest, each with a
defined JSON shape. ABDM uses release R4.
Base FHIR is deliberately loose, so countries constrain it with profiles. India’s are published by NRCeS, the National Resource Centre for EHR Standards, set up by the Ministry of Health and Family Welfare at C-DAC Pune. Its implementation guide defines a profile for each kind of record, such as an OP consultation record, a prescription record or a discharge summary record, plus profiles for the resources inside them. The guide is versioned; note which version you built against, because profile details change between releases.
It helps to be clear about where FHIR is used, because the word suggests more than ABDM asks for:
- FHIR is the record format. When a Health Information Provider shares a record, the payload is a FHIR document.
- FHIR is not the transport. Providers do not expose FHIR REST servers to each other. The ABDM APIs for linking, consent and data requests are ABDM’s own JSON, and the FHIR document travels inside them, encrypted.
- Your database does not have to be FHIR. Most hospital systems keep their own schema and build a bundle when a record is finalised.
If you are unsure where this fits in the wider flow, the HIP versus HIU guide shows who builds bundles and who reads them.
02Anatomy of a bundle
FHIR has several bundle types. ABDM accepts only document: a
self-contained, finalised record whose first entry is a
Composition acting as its table of contents. HL7 describes the
pattern in its
documents
chapter. Stripped to the parts that matter, a lab report looks like this:
{
"resourceType": "Bundle",
"type": "document",
"timestamp": "2026-09-03T10:15:00.000Z",
"identifier": { "system": "https://ndhm.in/bundle", "value": "3f1c…" },
"meta": {
"profile": [
"https://nrces.in/ndhm/fhir/r4/StructureDefinition/DocumentBundle"
]
},
"entry": [
{
"fullUrl": "urn:uuid:9a17…",
"resource": { "resourceType": "Composition", … }
},
{
"fullUrl": "urn:uuid:5b20…",
"resource": { "resourceType": "Patient", … }
},
{
"fullUrl": "urn:uuid:c471…",
"resource": { "resourceType": "Practitioner", … }
},
{
"fullUrl": "urn:uuid:0e88…",
"resource": { "resourceType": "DiagnosticReport", … }
},
{
"fullUrl": "urn:uuid:77d2…",
"resource": { "resourceType": "Observation", … }
}
]
}
Four properties of this shape are load-bearing:
-
typeisdocumentand the bundle carries atimestamp. -
meta.profilenames the NRCeS document bundle profile, and each resource inside declares its own profile the same way. Validators key off these URLs, so a correct shape under the wrong profile is still a rejection. -
Every entry has a unique
fullUrl, usually aurn:uuid:, and other resources refer to it by exactly that string. -
Everything referenced is included. A document is read
by someone who cannot call you back. If the report points at a
practitioner, that
Practitioneris an entry in the bundle.
03The Composition
The Composition says what the document is, who it is about,
who wrote it and how it is organised. The fields that matter most:
-
meta.profile— the NRCeS record profile for this kind of document, for example the diagnostic report record profile. -
type— a SNOMED CT code for the kind of record. A PHR app uses it to decide how to file and display the document, so an OP consultation coded as a discharge summary lands in the wrong place in the patient’s timeline. -
subject— a reference that must resolve to thePatiententry in the same bundle. -
status—finalfor a record being shared. -
authorandcustodian— the practitioner or organisation that wrote it, and the facility responsible for it. -
section— at least one, each with its own SNOMED CT code andentryreferences pointing at the clinical resources.
"resource": {
"resourceType": "Composition",
"meta": {
"profile": [
"https://nrces.in/ndhm/fhir/r4/StructureDefinition/DiagnosticReportRecord"
]
},
"status": "final",
"type": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "721981007",
"display": "Diagnostic studies report"
}
],
"text": "Diagnostic studies report"
},
"subject": { "reference": "urn:uuid:5b20…" },
"author": [ { "reference": "urn:uuid:c471…" } ],
"title": "Complete blood count",
"section": [
{
"code": { "coding": [ { "system": "http://snomed.info/sct", "code": "721981007" } ] },
"entry": [ { "reference": "urn:uuid:0e88…" } ]
}
]
}
Record profiles differ in which sections they expect. An OP consultation, for instance, has slices for chief complaints, physical examination, medications, investigations advised and follow-up, each identified by its own SNOMED CT section code. Read the profile for each record type you produce rather than generalising from one.
04Health information types
ABDM classifies records by health information type, or hiType. Consent is granted per hiType, care contexts are listed by hiType, and each hiType corresponds to one NRCeS record profile and one Composition type code:
| hiType | Composition type (SNOMED CT) | Typical source |
|---|---|---|
OPConsultation | 371530004 — Clinical consultation report | OPD visit notes |
Prescription | 440545006 — Prescription record | Medicines prescribed |
DiagnosticReport | 721981007 — Diagnostic studies report | Lab and imaging reports |
DischargeSummary | 373942005 — Discharge summary | Inpatient discharge |
ImmunizationRecord | 41000179103 — Immunization record | Vaccinations given |
HealthDocumentRecord | 419891008 — Record artifact | Scanned or unstructured documents |
WellnessRecord | 419891008 — Record artifact | Vitals, lifestyle and wellness data |
Invoice | No SNOMED CT code; type text only (“Invoice Record”) | Bills |
Three points follow from the table. First, the Composition type code
must match the hiType of the care context the record is
filed under; a mismatch is a rejection. Second,
HealthDocumentRecord and WellnessRecord share a
code, so the profile in meta.profile is what tells them apart.
Third, an invoice carries no type coding at all. Borrowing a nearby code to
fill the gap files every bill as a lab report.
The hiType also appears outside the bundle. In ABDM v3 discovery and linking responses, care contexts are grouped by hiType, with the type given once at group level. A HIP building those payloads needs the hiType stored on each care context, not derived later from the bundle.
05Terminology
Structure makes a record parseable; codes make it computable. ABDM records lean on two main code systems:
-
SNOMED CT (
http://snomed.info/sct) for clinical concepts: complaints, findings, diagnoses, procedures, section and document types. -
LOINC (
http://loinc.org) for laboratory tests and vital signs. Haemoglobin in blood by mass per volume, for example, is LOINC718-7.
Units belong in UCUM where they can be expressed there, such as
g/dL or mm[Hg]. The NRCeS condition profile also
allows ICD-10 for diagnoses, which helps hospitals that already code
discharges that way.
When you do not have a code
A CodeableConcept can carry text alone, and that
is valid. In practice many Indian hospital records are typed by operators
as “Hb”, “S. Creatinine” or “F.B.S.”,
and a sensible approach is:
- Use a code the clinician or lab system supplied, if it is from a system the profile allows.
- Otherwise match the name against a small, verified table of common tests and diagnoses.
- Otherwise send the text, and keep the original words in
textin every case.
Be careful with ambiguous names. “Sugar” could be fasting, random or post-prandial blood glucose, or a urine test; “albumin” could be serum or urine. Each has a different LOINC code, and choosing one silently turns an honest text record into a wrong coded one. Fuzzy matching makes this worse, not better.
06PDFs and scanned records
A great deal of Indian hospital documentation exists only on paper, or as
a PDF produced by lab equipment. FHIR carries these as an
Attachment: base64 data with a content type of
application/pdf. Where it goes depends on the record:
-
A lab report with structured results can carry its PDF in the
DiagnosticReport’spresentedForm. - A scanned prescription can travel as a
Binaryresource. -
Anything else, such as scanned discharge papers or an outside report, is
usually a
DocumentReference, and a record that is only a PDF fits theHealthDocumentRecordhiType.
Validate the bytes, not the label. HMIS uploads regularly mislabel a JPEG,
a Word file or an HTML error page as a PDF, and base64 stored in a text
column is often truncated. Check that the decoded file starts with
%PDF- and ends with a %%EOF trailer, and cap its
size: the whole bundle is encrypted and pushed in one piece, so a smaller
record is a more reliable transfer. A broken attachment is invisible until
the patient taps it on their phone, months later.
07Why bundles get rejected
Full FHIR validation against every StructureDefinition is slow and, with national profiles, occasionally stricter than the receiving systems. The failures that actually occur are a short list, and worth checking on every bundle before it is stored:
| Problem | What it looks like | Fix |
|---|---|---|
| Wrong bundle type | collection or transaction instead of document, or no timestamp | Build a document bundle |
| Composition not first | Entries sorted by resource type, or the Composition missing | Insert the Composition as entry[0] |
| Missing profile | No NRCeS URL in meta.profile | Stamp profiles on the bundle, the Composition and each resource |
| Dangling reference | A reference that matches no fullUrl in the bundle | Include the resource, or remove the reference |
| Unresolved subject | The Composition’s subject points at nothing | Reference the Patient entry’s fullUrl |
| Type mismatch | Composition code disagrees with the care context’s hiType | Derive the code from the hiType |
Duplicate fullUrl | Two entries share one URN, so references are ambiguous | Mint a fresh UUID per entry |
| Invalid JSON numbers | NaN or Infinity from a failed calculation | Reject the value before building |
Timing matters as much as the checks. An error found when the record is
created can be shown to the person who typed it. The same error found when
a consent request arrives becomes an ERRORED care context in a
transfer, a gap in the patient’s app, and nobody left who remembers
the record.
08From bundle to wire
A bundle never travels in the clear. When a HIP releases records under consent, each bundle is encrypted with ABDM’s Fidelius scheme before it is pushed to the HIU:
- The HIU generates an ephemeral key pair and a random nonce, and sends the public key and nonce with its data request.
- The HIP generates its own ephemeral key pair and nonce, and performs ECDH over Curve25519 to get a shared secret.
- The two nonces are combined to give a salt and an initialisation vector, and HKDF-SHA256 derives a 256-bit key from the secret.
- The serialised bundle is sealed with AES-256-GCM, which authenticates as well as encrypts.
- The HIP sends the ciphertext with its own public key and nonce; the HIU repeats the derivation and decrypts.
One detail causes a disproportionate share of failed integrations. ABDM uses Curve25519 in its short-Weierstrass form, with 65-byte uncompressed public keys, as the Java BouncyCastle library exposes it. The key-agreement functions most other libraries offer for this curve work in the Montgomery form and produce a different shared secret. The encryption appears to succeed and fails only at the far end, where the HIU cannot open anything. Test against another participant’s implementation, not just your own.
09Building it in practice
A few choices save the most time:
- Build from your own data, per hiType. Write one builder per record type you produce, fed from your existing tables, rather than a general FHIR layer for the whole HMIS.
- Be wary of strict model libraries. Typed FHIR libraries model base R4; national extensions and slices sometimes fight them. Plain JSON plus an explicit validator for the checks above is often easier to keep correct.
-
Generate URNs at build time. Mint every
fullUrlwhen assembling the bundle and substitute references in one pass, so dangling references cannot happen by construction. - Keep a test bundle per hiType and run it through your validator in CI, alongside one real bundle received from another sandbox participant.
- Store the bundle you sent. When a patient or an auditor asks what was shared, re-generating it from today’s data gives the wrong answer.
For where FHIR generation sits among the other steps of a hospital integration, see integrating ABDM with an HMIS.
Rather not hand-build bundles? The connector accepts plain fields or your own bundle, validates it and handles the encryption; see FHIR integration and, for labs, the lab page.