Why a FHIR API is wanted from systems that only send v2
An app, an analytics pipeline or a partner asks for FHIR. The data is in a laboratory, radiology or scheduling system whose only way of saying anything is HL7 v2: a message when a patient arrives, when a result is final, when a slot is booked.
In the United States the electronic health record usually has a FHIR API already, because certification requires one and CMS’s Promoting Interoperability programs require certified health IT. Certification is voluntary and covers the products submitted to it. A system outside it has no API by that route.
v2 is not making way: in ONC’s 2023 survey, 96% of health information exchanges received v2 admission messages. Nothing published measures what share of a hospital’s own interfaces are v2, so this article does not guess. HL7 is four standards explains why v2 persists; this starts where that ends.
Three ways to put FHIR in front of v2
Ask the source, build from the feed, or convert on the way through. The choice decides what the API can answer.
Three shapes of a FHIR front
StructureThe choice decides what the API can answer. Pick a shape to see its request path.
Request path
Identity queries- FHIR client
- FHIR search
- Front
- v2 query (QBP)
- Source system
- v2 reply (RSP)
- What it can answer
- Whatever the source holds, as of now.
- Where it breaks
- Only works if the source answers v2 queries. Nothing published measures how many systems do.
- Seen in
- IHE’s identity profiles: PIXm in front of a PIX manager, PDQm as a proxy to a PDQ environment.
Request path
Most v2 systems- Source system
- ADT, ORU, SIU
- Front keeps state
- FHIR search
- FHIR client
- What it can answer
- What was sent since the front started listening, or replayed into it.
- Where it breaks
- Anything the source never sent, history before go-live, and messages that arrive late or twice.
- Seen in
- The FHIR Front for HL7 v2 Systems, and most fronts over systems that only send.
Request path
A step, not an API- Source system
- One v2 message
- Converter
- One FHIR Bundle
- Something else stores it
- What it can answer
- Nothing by itself. It produces resources for another system to store.
- Where it breaks
- State: each Bundle describes one message, not the patient’s current record.
- Seen in
- Azure’s $convert-data, Microsoft’s FHIR-Converter, the LinuxForHealth converter. Google’s Cloud Healthcare API stores and parses v2 but its concept page does not convert it.
IHE PIXm 3.1.0 and PDQm 3.2.0; Microsoft Learn, $convert-data (August 2026); Google Cloud, HL7v2 concepts; project READMEs, read November 2026.
Asking the source needs a v2 query interface, which IHE describes only for identity. Converting on the way through produces one bundle per message and leaves the store to someone else: Microsoft says its default $convert-data templates are not intended for production. So most fronts build from the feed, and so does the FHIR Front.
What HL7’s v2-to-FHIR guide gives you
HL7 Version 2 to FHIR reached STU 1 in October 2025. It maps messages, segments, data types and code tables to FHIR R4 as 263 ConceptMaps a program can read, and calls itself “a starting point for implementations”. Every page is informative, and Z-segments “will not be considered for inclusion”.
What the guide maps
Read 2026-11-04HL7’s v2-to-FHIR guide, counted from its published package. Pick an everyday event to see whether the guide maps it and what it creates.
- Maps in the guide
- 263
- Message structures
- 13
- Segments
- 75
- and 105 data types
- Code rows without a FHIR code
- 219
- of 2,496, across 70 code tables
Event
- Message structure
- ADT_A01
- Message map
- In the guide
- Encounter.status
- unknown
Resources the map creates
- MessageHeader
- Provenance
- Patient
- Account
- Observation
- CareTeam
- RelatedPerson
- Encounter
- Basic
- Coverage
- AllergyIntolerance
- Condition
- EpisodeOfCare
- Procedure
Shares ADT_A01 with admit and register. The guide’s table sets Encounter.status to unknown for it.
hl7.fhir.uv.v2mappings 1.0.0 (STU 1) and HL7 table 0354 from hl7.terminology.r4 6.5.0, read by scripts/read-v2-fhir-coverage.mjs.
Two everyday events have no message map: a discharge (A03) and a merge (A40). And 219 of the 2,496 code rows have no FHIR code. The FHIR Front starts from the guide and adds maps of its own where it has none.
From a stream of events to current state
A v2 feed sends events. A FHIR server holds state. The guide maps one message at a time and leaves existing resources to the implementer, so most of the work in a front is here. Try it: an update (A08) after an admission.
Events into state
The guide’s own tablesA v2 feed sends events; a FHIR server holds state. Build a sequence and compare the guide’s status table applied as it stands with a front that keeps state.
Resource
Add a message (5 of 8)
| # | Message | Guide’s table, as it stands | Kept as state |
|---|---|---|---|
| 1 | A01 Admit | in-progress | in-progress |
| 2 | A08 Update patient | unknown | in-progressDiffers |
| 3 | A02 Transfer | in-progress | in-progress |
| 4 | A08 Update patient | unknown | in-progressDiffers |
| 5 | A03 Discharge | finished | finished |
2 of 5 steps differ. The only rule added: an update (A08) does not say where the patient is in the visit, so a front keeps the status it had. The guide’s row sets unknown, which FHIR calls “a value of last resort”.
hl7.fhir.uv.v2mappings 1.0.0: ConceptMaps table-hl70003-to-encounter-status and table-hl70085-to-observation-status, every row, read by scripts/read-v2-fhir-coverage.mjs. FHIR R4 encounter-status.
Repetition is the other half. v2’s control chapter says “only the use of sequence numbers can prevent duplicate transactions”, and they are optional. FHIR’s conditional update, a PUT addressed by an identifier, makes a repeated message harmless; it is trial use in R4. And the acknowledgement is a promise: send it only once the message is stored.
When to acknowledge
StructureIn v2, a commit accept tells the sender it no longer has to resend. The same failure, played through two orderings.
- 1SourceSends ORU^R01, control id 4417
- 2FrontWrites the message to durable storage
- 3FrontReturns CA (commit accept), echoing 4417
- 4FrontRestarts before mapping (failure)
- 5FrontOn restart, maps the stored message
Nothing is lost. The message was stored before the source was told it could forget it.
- 1SourceSends ORU^R01, control id 4417
- 2FrontReturns CA (commit accept), echoing 4417
- 3FrontRestarts before writing anything (failure)
- 4SourceHas its acknowledgement, so does not resend
The result is gone. The source was released from resending a message the front never kept.
HL7 v2 control chapter, acknowledgement rules (v2+ draft text): with a positive accept acknowledgement “the receiving system commits the message to safe storage in a manner that releases the sending system from the need to resend the message.”
The FHIR Front keeps current state and acknowledges a message once it is stored (Phase 1).
Identifiers and codes on the way in
US Core requires every Patient identifier to carry a system URL. The guide sets it only for authorities in HL7’s identifier registry, so a hospital’s own record number gets assigner and no system, and fails US Core. Microsoft’s converter fills the gap with a dummy URL “to be replaced … by the end users”. The fix is a table the front owns: one URL per assigning authority.
The guide maps the merge segment only to Account, and none of its maps writes Patient.link. Merges, and local codes, are covered in Terminology and patient identity services. The FHIR Front configures a system URL per authority and links merged records (Phase 1), and applies your code maps (Phase 2).
What the API can and cannot answer
A front built from a feed knows what was sent after it started listening. History has to be replayed from the interface engine’s archive, and the arithmetic is worth doing early.
Loading history
Your numbersA front built from a feed starts empty. Enter your own volumes to see how big a replay is, how long it takes, and how much of the window a caller wants the API holds without one. The starting values are examples, not typical figures.
Measure this on your own front: it depends on the maps, the store and the hardware.
- Messages to replay
- 7,300,000
- a day’s volume × days of history
- Replay time
- 20.3 h
- backlog ÷ processing rate, end to end
- Window held without a replay
- 8%
- 30 of 365 days
Arithmetic only. Messages are replayed in order through the same path as live ones, so the rate is the front’s own, not the archive’s.
US Core says what a server has to support. Appointment is not in it, and the SHALL on creating a DiagnosticReport applies only to reports and notes, not laboratory results.
What US Core asks of a server
Read 2026-11-04For each resource a v2 feed fills, what the US Core server CapabilityStatement says a server SHALL support. Certification requires 6.1.0; 9.0.0 is the newest version it allows.
Version
Patient
In US CoreInteractions, SHALL
- search-type
- read
Searches, SHALL
- _id
- identifier
- name
- birthdate + name
- gender + namethis version only
US Core Patient also requires identifier.system on every identifier, which a local record number does not get from the v2-to-FHIR guide alone.
Encounter
In US CoreInteractions, SHALL
- search-type
- read
Searches, SHALL
- _id
- patient
- date + patient
ServiceRequest
In US CoreInteractions, SHALL
- read
Searches, SHALL
- patient
- _id
- patient + category + authored
- patient + category
- patient + code
Observation
In US CoreInteractions, SHALL
- search-type
- read
Searches, SHALL
- patient + category + date
- patient + category
- patient + code
DiagnosticReport
In US CoreInteractions, SHALL
- create
- search-type
- read
Searches, SHALL
- patient
- patient + category + date
- patient + category
- patient + code
The SHALL on create applies only to US Core’s report and note profile. For laboratory reports it is MAY, so a read-only front of results misses nothing here.
Appointment
Not in US CoreUS Core has no Appointment profile and no entry for it in the server CapabilityStatement, in either version. A front serves it as base FHIR.
US Core server CapabilityStatement, hl7.fhir.us.core 6.1.0 and 9.0.0 from packages2.fhir.org, read by scripts/read-us-core-searches.mjs.
Say all of this in the CapabilityStatement, which FHIR: the API is the easy part explains, and point every resource at its message with a Provenance, as the guide suggests. Authorization is the front’s to provide, since the source has no idea who is asking; An AI agent is a client, not a user covers scopes. The FHIR Front authorizes every request with SMART Backend Services and replays archives (Phase 1).
Testing a front on your own feed
HL7 publishes test messages and no tool to check a conversion: it “has no plans to either create, nor certify any transform validator at this time.”
Testing a front
0 of 4 checksHL7 provides test messages and no tool to check a conversion. Four checks cover most of it. Tick them off for your own front; nothing is stored.
- Passing proves
- The resources are shaped the way US Core says, with codes from the right value sets.
- It does not prove
- That a value is the right one for the patient. A valid Observation can carry the wrong result.
A front built from the guide alone fails first on Patient.identifier.system.
- Passing proves
- Admissions, discharges and results received match encounters and observations held, so nothing was dropped.
- It does not prove
- That what was kept is current. Counts match on a store full of stale statuses.
HL7’s guide names the risk: “separate V2 and FHIR worlds will be out of step”.
- Passing proves
- The same messages in the same order give the same resources, so a bad map can be fixed and rerun.
- It does not prove
- That the order the feed sent was the order things happened.
Without it, a wrong map can only be patched, never repaired.
- Passing proves
- State survives what live feeds do: updates, cancels, corrections, merges and repeats.
- It does not prove
- That every local variation is covered. Your feed has its own.
HL7 Version 2 to FHIR 1.0.0, Validation page: test messages with their bundles, and no transform validator. The HL7 v2 Message Creator sends the hard cases to a system under test.
The HL7 v2 Message Creator sends those cases to a system under test, and the FHIR Front’s mapping workbench shows the rule behind each element (Phase 1).
Where the standards stop
Transport and field mappings are standardised. How a front keeps state, how current it is and how it loads history are not.
Writing back is further out: HL7’s guide maps one way, and no specification turning a FHIR order into a v2 order was found. The FHIR Front does not plan it. Notifications are nearer, through HL7’s Subscriptions R5 Backport (Phase 2).
Elsewhere the pattern holds and the levers differ. Ontario’s laboratory system publishes a v2 interface and a view-only FHIR R4 guide; Canada’s CA Core+ is still a draft, so the FHIR Front leaves it to Phase 2.

