FHIR conditional create: when If-None-Exist stores nothing, and when it stores two

If-None-Exist is meant to stop duplicates. It answers 200 and stores nothing when a match exists, and in our tests one server still stored two copies from one transaction. Here is how to tell what happened.

The short answer

A FHIR conditional create (POST with an If-None-Exist header) creates the resource only if no existing resource matches the search. If exactly one matches, R4 says the server ignores the post and returns 200 OK, creating nothing; if more than one matches, it returns 412.1 So a 200 here doesn’t mean “saved”, and a 201 doesn’t promise there’s only one copy. In our October 2026 study, all four server setups answered 200 to a conditional create that matched one resource and stored nothing3, and one setup stored two copies from two conditional creates on the same identifier inside one transaction.3 After any conditional create, search by the identifier and count.

A FHIR server can accept a write and quietly drop a field. Deedglass reads the resource back and names what differs. Made-up resource.

What R4 says about If-None-Exist

The header carries a search, typically on a business identifier:

Code A request one program sends to another over the web.

POST /fhir/Patient
If-None-Exist: identifier=https://hospital.example/mrn|00042
Content-Type: application/fhir+json

R4 defines three outcomes:1

Matches found Server does Reply
None Creates the resource 201 Created
Exactly one Ignores the post, creates nothing 200 OK
More than one Refuses 412 Precondition Failed

The middle row is the one that surprises people. The request “succeeded” in the sense that the server did what the spec says. But the body you sent was not stored. If your body differed from the existing resource (a corrected phone number, a new address), that change is gone, and the reply looks like success.

Case 1: 200 OK and nothing stored

On 26 October 2026 we sent synthetic writes to four setups: HAPI FHIR JPA Server 8.12.0 run locally twice (starter defaults, and with request validation on), the HAPI public test server (build 8.13.10-SNAPSHOT) and the Firely Server 6.11.0 public sandbox, and read every one back.3

All four answered 200 to a conditional create that matched one existing resource, and stored nothing.3 That’s R4 behaviour, not a defect. The point is narrower and more practical: the reply alone can’t tell your code whether anything was written.

A client that treats any 2xx as “stored what I sent” will log success for a write that didn’t happen. An agent that reports “created patient record” back to a person will be wrong, confidently.

Case 2: two copies from one transaction

The opposite failure turned up in a transaction. We put two conditional creates on the same identifier in one transaction Bundle. The Firely sandbox stored two copies; HAPI stored one.3 Behaviour here depends on the server and its configuration, so we name versions and use neutral wording; our study page has the raw runs.

R4 does set rules for transactions: entries are processed as a unit, and the server must resolve references between entries in the Bundle.2 It’s easy to assume that “conditional” plus “transaction” means “one copy, enforced by the server”. Our results say check, don’t assume.

What you sent HAPI 8.12.0 Firely 6.11.0 sandbox
Conditional create, one match exists 200, nothing stored 200, nothing stored
Two conditional creates, same identifier, one transaction One stored Two stored

Sources:3,3.

How to tell what happened

Three checks, in order of cost:

  1. Read the status code precisely. 201 means a resource was created; 200 to a conditional create means one already matched. Don’t collapse them into “success”.
  2. Read the Location header or the returned resource id, and fetch it. Compare it with the body you meant to store.
  3. Count by identifier. Search Patient?identifier=system|value after the write. One result is what you wanted. Zero means nothing was stored; two or more means duplicates you now need to resolve.

For a transaction, do the count for each identifier the Bundle touched, and check each entry’s response status rather than the Bundle’s overall reply.

Why this matters for agents

Agents retry. A timeout, a dropped connection or a model that isn’t sure the first call worked will send the same create again. Conditional create is the standard defence against that duplicate. It works when one match exists. But it also turns “I tried to update this patient’s details” into “I was told 200 and nothing changed”, and in a transaction it may not prevent the duplicate at all on every server.

Duplicates in patient data cost real money and time to clean up later; our posts on an agent that retried and wrote twice and on writes that said success show the general pattern. The rule that follows is simple: an agent’s write isn’t done when the server replies. It’s done when a read-back shows the record is in the state the agent meant.

A checklist

  • Use If-None-Exist on a stable business identifier, never on a name or date of birth alone.
  • Treat 200 and 201 as different outcomes in code and in logs.1
  • After a 200, fetch the matched resource and compare it with what you meant to write. If they differ, you need an update, not a create.
  • After any create, search by identifier and count. Alert on zero or on more than one.
  • In transactions, don’t rely on two conditional creates for the same identifier collapsing into one; send one entry per identifier.3
  • Record the reply, the read-back and the count together, so a reviewer can see what was checked.
  • Name the server, version and configuration whenever you write down how it behaved.

Questions people ask

Why does a conditional create return 200 instead of an error?

Because R4 defines it that way: when exactly one resource matches, the server ignores the post and returns 200 OK.1 It’s designed to make the create safe to repeat, not to tell you about changes in your body.

Does If-None-Exist update the existing resource?

No. It creates nothing when one match exists. To change the existing resource, use an update, ideally with If-Match on the version you read.

Can a transaction still create duplicates?

On one setup we tested, yes: two conditional creates on the same identifier in one transaction stored two copies on the Firely sandbox, and one on HAPI.3 Search by identifier after the write to be sure.

The FHIR write integrity study has every case and server. To watch a read-back catch a write that said success, run npx deedglass health pilot, a 15-minute pilot on synthetic data4; the healthcare overview explains the rest. 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.

The takeaway

If-None-Exist answered 200 and stored nothing on all four setups we tested, and one stored two copies in one transaction; count by identifier after every write.

Read nextFHIR transaction vs batch: what the spec requires, and what it leaves open Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1HL7 FHIR R4: RESTful API, conditional create · hl7.org · checked 27 October 2026
  2. 2HL7 FHIR R4: RESTful API, batch and transaction · hl7.org · checked 27 October 2026
  3. 3Deedglass research: FHIR write integrity 2026 · deedglass.com · checked 27 October 2026
  4. 4Deedglass for hospitals: the 15-minute pilot · deedglass.com · checked 27 October 2026

Published 27 October 2026, sources last checked 27 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing