The short answer
Whether a FHIR server refuses a write that references a resource that doesn’t exist depends on the server and how it’s configured. R4 says a literal reference “SHALL be resolvable”, but its RESTful API page doesn’t say what a create or update must do when it isn’t.1 In our October 2026 study, the Firely Server 6.11.0 sandbox accepted a reference to a Patient that doesn’t exist, and a transaction holding one, storing 3 of 3 entries; HAPI FHIR refused both with 400 and stored nothing from the transaction.4 If agents write to your server, resolve every reference before you send, and test what your server does.
Deedglass
The server said “saved”. Deedglass looked.
Deedglass reads it back
What R4 says
R4 separates two kinds of reference:1
| Kind | Example | Must the target exist? |
|---|---|---|
| Literal | "reference": "Patient/123" |
It SHALL be resolvable to an actual resource |
| Logical | "identifier": {"system": "...", "value": "00042"} |
No requirement; servers may accept, resolve or reject |
The literal rule describes what a correct reference is. It doesn’t spell out whether the server must check it on every write. The RESTful API page talks about referential integrity for deletes and for conditional references inside transactions, but not for a plain create or update that points nowhere.1 So enforcement is a design choice each server makes, and often a setting.
Inside a transaction, references between entries are resolved by the server, and a conditional reference that matches zero or several resources fails the whole transaction.3 A reference to a plain id that doesn’t exist anywhere is a different case, and that’s the one servers treat differently.
What we measured
On 26 October 2026 we sent synthetic writes to four setups: HAPI FHIR JPA Server 8.12.0 run locally twice (starter defaults; request validation on), the HAPI public test server and the Firely Server 6.11.0 public sandbox, and read every one back.4
| Write | Firely 6.11.0 sandbox | HAPI |
|---|---|---|
| Observation referencing a Patient that doesn’t exist | Stored | Refused, 400 |
| Transaction holding such a reference | Stored 3 of 3 | Refused, 400; nothing stored |
Source:4. Referential integrity is a server setting, so this describes those setups as we ran them, not every deployment of either product. The full case list is on the FHIR write integrity study.
How servers differ by design
Different products make this a visible choice. Google Cloud’s Healthcare API, for example, enforces referential integrity within a FHIR store unless it has been switched off: a create, update or patch fails if the result references a resource or version that doesn’t exist, and a delete fails if other resources point at it. It can be switched off with disableReferentialIntegrity when the store is created, and its bulk import method doesn’t enforce it, so references become consistent once everything has loaded.2
That last point shows why some deployments turn checks off: bulk loads arrive out of order, and a strict server would refuse an Observation that arrives a second before its Patient. The trade-off is that a lenient store will also accept a reference that never becomes valid.
Why a dangling reference is a problem
A reference to nothing isn’t harmless data. In practice:
- The record is orphaned. An Observation whose subject doesn’t exist won’t show up in that patient’s chart, because there is no such patient to query.
- It may be the wrong patient later. If an id is reused or created afterwards by another process, the reference starts pointing at a record nobody intended.
- Agents produce them easily. A model that writes
Patient/123from memory, or from a search result in a different system, will produce a syntactically perfect reference to a resource that isn’t there. - The reply looks like success. On a lenient server the write gets 201, the agent reports done, and nothing flags the gap.
None of this is about clinical judgement. It’s about whether the record links to what it claims to link to.
Checking references before and after a write
- Resolve before writing. For every literal reference in the body,
GETthe target. If any returns 404 or 410, don’t send the write. - Prefer references the agent just read. Take ids from resources fetched in the same step, not from model memory or another system’s ids.
- Use logical references deliberately. If you only have a business identifier, either resolve it to a literal id first or send it as an identifier reference, knowing servers may accept, resolve or reject it.1
- In transactions, use
urn:uuidfull URLs for resources created in the same Bundle, and let the server replace them.3 - Read back after the write and follow each reference in the stored resource. Every one should resolve.
- Know your server’s setting. Test once with a deliberate dangling reference against a sandbox and write down the version and configuration.
| Check | When | Catches |
|---|---|---|
| GET each referenced id | Before the write | References to nothing |
| Ids only from fresh reads | While building | Ids from memory or another system |
| Follow references on read-back | After the write | Anything a lenient server stored anyway |
| Sandbox test | Once per server version | What your server actually enforces |
Questions people ask
Does FHIR require servers to enforce referential integrity?
R4 says literal references shall be resolvable, but it doesn’t spell out that a server must refuse a create or update that points to a missing resource.1 Servers decide, often through a setting.
Why did HAPI refuse and Firely accept?
They’re configured differently in the setups we ran. HAPI refused both writes with 400; the Firely sandbox stored them.4 Either product may behave differently with other settings.
Is a logical reference safer?
It’s more honest about what you know, but R4 places no requirement that the target exists, and servers may accept it untouched.1 Resolve it when you can.
To watch a read-back catch a write that said success, run npx deedglass health pilot, a 15-minute pilot on synthetic data.5 The healthcare overview and provenance spec show how checks like these get recorded. To try approvals and read-backs on a real database in your browser, open the playground; to grade what an agent can reach on your own machine, run npx deedglass scan (the grade, explained).
Written with AI assistance and checked against our claims register.