Skip to content
DEXPRADEXPRA home

Guide · Interoperability

FHIR: the API is the easy part

A FHIR server answers its first request in an afternoon. Profiles, terminology and the implementation guide are the actual project, and none of them is the API.

DEXPRA · · 12 min read

Two hours in, something works

Nothing else in healthcare interoperability makes a good first impression. A developer who has never seen a clinical standard can point a browser at a public FHIR server, ask it for a patient by name, and get back JSON that a junior engineer could have designed. No association negotiation, no positional parsing, no reference information model to study first.

That first impression is accurate about the technology and badly misleading about the work. The API is genuinely simple, deliberately so. What is not simple is the agreement underneath it: which fields a receiver may rely on, which code systems the values come from, what a missing element means, and who is allowed to ask.

The optionality problem moved

Part 3 of this series set out the central weakness of HL7 v2. The standard leaves almost everything optional, so two systems can both be perfectly conformant and still be unable to exchange a usable message. The negotiation gets pushed into a per-site interface specification that nobody outside the project can read, and that is why a v2 interface is a project rather than a configuration.

FHIR did not remove that problem. It relocated it. Base resources are still permissive, and the negotiation still has to happen. What changed is where it is written down: in a published, versioned, machine-readable implementation guide rather than in a document exchanged between two vendors.

That is a real improvement, and it is worth being precise about why. A guide can be validated against automatically. It can be reused by a whole country rather than one project. It ships as an installable package with a version number. But it is not plug-and-play, and a plan that budgets for the API and not for the guide has budgeted for the wrong half of the work.

The API work is a small fraction of the effort on most FHIR projects. Profiling, terminology and validation are the majority of it. That is not a planning failure; it is what the work is.

What a resource actually is

The unit of exchange is the resource: a small, self-contained object with a type, an identity and a URL. R4 defines 145 of them. Patient, Observation, Encounter, Condition, MedicationRequest, DiagnosticReport, ServiceRequest and DocumentReference cover most of what a first project touches.

Base resources are built to a stated design rule. An element belongs in the base resource if roughly 80% of implementations everywhere in the world would use it, and everything else goes in an extension. That is why Patient has no field for ethnicity, religion or preferred ward, and it is why the base resources stayed small enough to learn in an afternoon. It is also why a base resource is almost never sufficient on its own.

The extension mechanism is the clearest single improvement FHIR made over its predecessors. An extension carries a URL that resolves to its own definition, so a receiver meeting one it has never seen can look up what it means. Set that beside the v2 Z-segment, which HL7 itself describes as opaque without prior manual explanation by the sender. Both let a site add local data; only one tells a stranger what the data is.

The rule that will bite you

A resource id is not a medical record number. It is a handle assigned by one server, unique within that server and meaningless anywhere else. The same patient has a different id on every system that holds them.

Business identifiers live in the identifier element, each carrying a system URL that names who issued it: the hospital that owns the MRN, the national scheme, the payer. This is the v2 patient identifier list with the assigning authority in a field that cannot be misread, and it is the reason cross-system matching is possible at all. Every deduplication and linkage design that starts from the resource id gets rebuilt later, usually after the duplicates are already in production.

Nine interactions, and three details

The RESTful API reached Normative status in R4 and is small enough to hold in your head. Nine interactions cover ordinary resources: read, vread, update, patch, delete, create, search, history and capabilities. Three more sit beside them: transaction, batch, and the $-prefixed operations for what REST cannot express, such as validating an instance or expanding a value set.

Call the last of the nine first. A GET on the server’s metadata endpoint returns a CapabilityStatement declaring which resources it exposes, which interactions it allows, which search parameters it actually implements and which profiles it claims. Servers routinely omit parameters the specification defines, and that document is where it is declared rather than discovered in integration testing.

Three details separate a working client from a fragile one.

  • Delete does not mean gone. A deleted resource returns 410 on read, but its history remains addressable and a later update can bring it back. Anything you build for a retention or erasure requirement has to be designed at the storage layer.
  • Update is a whole-resource replace. A client that reads a Patient, changes the phone number and writes back only the three fields it understands has just deleted the other twenty. Read, modify what you read, write it back in full, or use PATCH.
  • Concurrency is available and almost never used. Every read returns an ETag carrying the version id. Send it back as If-Match and the server rejects a write that would clobber a concurrent one. Two systems writing to the same record without it overwrite each other quietly, at a rate proportional to traffic.

Search is not a contract

Search is where FHIR stops resembling a generic REST API. The query grammar is large and defined per resource type, and four features decide whether a screen renders in forty milliseconds or times out: _include to pull referenced resources into the same response, _revinclude to pull in what points back at you, chaining to filter on a property of the referenced resource, and following the server’s own paging links rather than constructing offsets.

The part that surprises people is that a server is permitted to return more than you asked for. It may ignore a parameter it does not support, and it applies its own matching rules for string parameters. The specification is explicit that clients must be prepared for this. Filtering that matters clinically or legally has to be re-applied on the client, and the alternative is a screen that is silently wrong against one vendor and correct against another.

Profiling is the project

A base resource says what may be sent. A profile says what must be. It is a StructureDefinition that constrains a resource for a context: tightening cardinality, binding an element to a value set, adding extensions, and marking which elements a conforming system has to be able to handle.

Profiles constrain rather than extend. A profile can make an optional element required or forbid one outright, but it cannot invent a new element outside the extension mechanism and cannot relax anything the base resource requires. That restriction is what keeps a profiled instance readable by a generic client: degraded, but readable.

Three pieces of the mechanism account for most of the difficulty.

The parts of a profile that projects underestimate.
MechanismWhat it doesWhere it goes wrong
Binding strengthSays how firmly an element is tied to a value set: required, extensible, preferred or exampleAn example binding carries no obligation at all, and is regularly read as though it did
SlicingSplits a repeating element into named cases with their own rules, told apart by a discriminatorA wrong discriminator makes validation pass everything or fail everything, which is why first profiles break here
Must supportFlags an element a conforming system has to be able to populate and processBase FHIR does not define the flag at all. Its meaning comes from the guide, and it does not mean the element will be present

Must support is worth one further sentence, because it is the most consistently misread word in the specification. The base standard says in terms that a guide using the flag must define what supporting an element obliges a system to do. US Core defines it precisely: a conforming system must be able to populate the element when it has the data and process it when it arrives, and where the data is genuinely absent it may have to say so explicitly rather than omit the element, so that "we do not have this" stays distinguishable from "we do not support this".

Terminology is a service, not a table

The structure of a resource is the easy half of its meaning. The other half is in the codes, and terminology is reliably the workstream discovered late and estimated low. A blood pressure is not interoperable because it is an Observation. It is interoperable because it carries a LOINC code, with components in mmHg expressed in UCUM.

FHIR models this as first-class resources rather than lookup tables. A CodeSystem defines codes, a ValueSet selects the ones usable in a particular place, and a ConceptMap relates one system’s codes to another’s. Around them sits a small set of operations a terminology server implements and your application calls: expanding a value set, validating a code against one, looking a code up, testing whether one code subsumes another, and translating between systems.

Four things surprise teams, in roughly this order.

  • Terminology has licensing. LOINC is free with registration. SNOMED CT requires a licence, covered nationally in member countries and not elsewhere. CPT is licensed by the American Medical Association. These are procurement questions with lead times, not sprint tasks.
  • Value sets are not static. An expansion is a point-in-time answer against a particular version. Two systems validating against the same value set at different times can legitimately disagree.
  • CodeableConcept is not a code. It is a set of codings from possibly several systems, in no guaranteed order, plus free text that may be the only accurate part. Reading the first coding and ignoring the rest is a common source of quietly wrong analytics.
  • Units are a terminology too. UCUM is a grammar rather than a list, and mg/dL is not interchangeable with mmol/L without a conversion that depends on the analyte. Numeric comparisons across sources without unit handling produce results that look fine and are not.

Guides ship as packages

An implementation guide is not a PDF. It is a versioned package of machine-readable conformance resources published at a canonical URL and installable by a tool, and this is the mechanical fact that separates FHIR from every previous attempt at healthcare specification. Guides depend on other guides the way libraries depend on libraries, and they are pinned by version for the same reason. Claiming conformance to US Core without a version number is about as precise as saying you use React.

The authoring chain is ordinary software tooling. Profiles are written in FHIR Shorthand, a small text language that lives in version control and produces a reviewable diff. A compiler turns that into conformance resources, and the IG Publisher validates them, generates the human-readable site and produces the installable package. The website a person reads and the package a validator consumes are generated from one source, so they cannot disagree.

Which makes conformance something you can put in continuous integration, and that is the point. Pin the guide version, run the validator against your examples and a sample of real output, and a breaking change appears in a build rather than in a partner’s bug report six months later.

An instance that passes base FHIR validation has proved almost nothing, because base FHIR permits nearly everything. The meaningful question is whether it validates against the profile in the guide you claim, with terminology checking switched on.

Which release, and why R4

FHIR has been through six numbered releases in twelve years, and the version question has a boring answer worth stating before the detail: build on R4. Not because it is newest, but because it is the one regulation names, the one the guides target, and the one every production server speaks.

The releases, and what each is good for in 2026.
ReleaseStatus
DSTU1 and DSTU2Historical. DSTU2 is still found behind older apps
STU3Deployed in places, particularly outside the United States
R4 (4.0.1)The one to build on. First release with Normative content, and the exact version US regulation names
R4BA maintenance release between R4 and R5, not a migration target
R5Published and stable. Adoption is thin, because nothing requires it and the guides have not moved
R6In ballot as of mid-2026. Not published, and not something to build against

US Core reached STU 9 in May 2026 and still builds on R4, which is a useful signal about where the centre of gravity actually is. The reason is not technical conservatism. One US certification criterion names five specifications by version number, including FHIR 4.0.1, and every certified electronic health record ships that API or loses its certification. Regulation is what moves this ecosystem, and it has not moved.

Scale, and what FHIR is not

REST is built for one patient at a time. A quality measure, a research cohort or a training set needs a million, and the way to get them is not a loop over the API. Bulk Data Access exists for exactly this: a kick-off request, a 202 with a polling URL, and eventually a manifest of newline-delimited JSON files to land in object storage. Using it rather than the REST API is the largest single performance decision most data projects make.

What happens to those files is the second surprise. Resources are deeply nested, heavily polymorphic and full of repeating elements, which is right for exchange and hostile to a SQL analyst. Keeping the originals for provenance and replay is correct; treating them as the storage model for an analytics platform is the expensive mistake, and it is usually made by default rather than decided. Project them deliberately, once, whether into declared views or into a clinical data model of your own.

The same restraint applies to what FHIR claims. It is an interchange format, not a database schema. It defines what is exchanged and deliberately not who may ask, which is what SMART on FHIR supplies with OAuth 2.0 and OpenID Connect. And it is not a migration path off v2: there is no business case, no regulatory driver and no vendor roadmap for switching off working interfaces. The realistic architecture is v2 in the core and FHIR at the edges, with a mapping layer between them that you will own.

Where model output belongs

FHIR is described often enough as the foundation of clinical AI that it is worth being precise. It gives a machine learning project three real things: a population extractable in bulk, a vocabulary that means the same thing at two sites, and a route back into the clinician’s workflow. It gives no model registry, no feature store and no training format, and no supplement proposes one.

Output belongs in the record, in a resource that carries its own provenance. A derived value or classification is an Observation, with derivedFrom pointing at the inputs. A predicted probability over a stated horizon is a RiskAssessment, which is where risk scores belong and where they are routinely not filed. A flag for human attention is a DetectedIssue. Provenance records what produced the result and from which inputs, and Device identifies the software and its version. There is no resource for a model itself, and Device describes the software that ran rather than the thing that was trained.

Two dataset traps are worth naming because they validate beautifully and fail in deployment. The last-updated timestamp in a resource’s metadata records when it was written to that server, not when anything happened clinically; the clinical time is in the effective, onset and recorded fields. And status matters: entered-in-error, preliminary, amended and cancelled are all normal states in a live record, and a cohort that treats them as final includes data the clinicians have already retracted.

A model that writes to its own database is invisible to care, exactly as a segmentation written to a storage bucket rather than back into the study is invisible to a radiologist. The two routes that put an inference in front of a clinician are a resource written into the record and a CDS Hooks card raised at the moment of the decision. Everything else is a dashboard.

Four standards, one hospital

FHIR does not arrive in an empty building. It arrives in a hospital already running v2 interfaces, exchanging C-CDA documents, and storing images in an archive that speaks DICOM. Each of those is still the current answer to a job the others do badly.

Real-time notification inside the enterprise is v2, and there is no pressure changing that. Attested, signed, human-readable artefacts are documents, and a CDA document travels inside a FHIR DocumentReference unchanged. Imaging is DICOM: a FHIR ImagingStudy is an index that points at the study and its retrieval endpoint, not a container for pixels. Anything new, anything crossing an organisational boundary, and everything regulation names is FHIR.

The imaging seam is where this whole series meets. The order and the patient are HL7’s, the study and the pixels are DICOM’s, the identifiers have to cross the line intact, and the profile specifying exactly how is IHE’s. Part 1 covers the imaging standard, Part 2 the profile that specifies the handoff, and Part 3 the four generations of HL7 that FHIR is the youngest of.

The full 26-page paper goes further in every direction: the search features that decide an architecture, slicing and discriminators, the accelerator programmes that produce the domain guides, the SMART launch sequence and backend services, the export flow end to end, and nine mistakes that are all cheaper to avoid than to discover in integration testing.

Working on one of these problems?

We’re publishing more than we’re consulting right now. If you have something specific in interoperability, data platforms, or clinical AI, tell us what it is and we’ll tell you what we think.

Get in touch