A FHIR server returns HTTP status codes that mostly mean what they mean in vanilla HTTP. The word mostly is the trap. A handful of codes carry FHIR-specific semantics on top of the base HTTP meaning, and a client that ignores those specifics will misread server intent in ways that only surface under load.
The site's FHIR RESTful API cheatsheet has a status-code section. For the wider REST framing, more FHIR references collects related pieces.
The Codes FHIR Uses As-Is
- 200 OK — successful read, update, search, or transaction commit
- 201 Created — successful create with Location header
- 204 No Content — successful delete
- 400 Bad Request — malformed request, usually with an OperationOutcome
- 401 Unauthorized — auth required or invalid
- 403 Forbidden — auth valid but scope insufficient
- 404 Not Found — resource missing or deleted
- 405 Method Not Allowed — verb the server does not support here
- 500 Internal Server Error — server broke
Nothing unusual there. A vanilla REST client understands these fine.
The Codes FHIR Reads With Extra Meaning
- 304 Not Modified — the conditional-read success path, no body
- 409 Conflict — resource state conflict, often around concurrent creates or references
- 410 Gone — resource was deleted, not just missing
- 412 Precondition Failed — conditional update or delete rejected because ETag or If-None-Exist mismatch
- 422 Unprocessable Entity — validation failure with an OperationOutcome
The 304 is the conditional-read payoff — covered in the read-side deep dive. The 412 is the conditional-update payoff — covered in the write-side deep dive. The 410 is subtle: FHIR distinguishes a soft-deleted resource from one that never existed, and returning 410 on the deleted case gives clients enough information to avoid retry loops. Servers that flatten deleted-to-404 lose that distinction and their clients pay for it.
OperationOutcome Rides Along
Any error status can carry an OperationOutcome resource in the body. It is a FHIR-native error type with issue.severity, issue.code, issue.diagnostics, and optional expression pointers. A well-behaved client parses OperationOutcome for the details rather than the raw HTTP status alone.
That parse is what turns a 400 from "bad request" into "PDF attachment exceeds server size cap of 10 MB." Same code, hugely different actionability.
The Special Case: Batch and Transaction
Transaction Bundles map their overall status to a single HTTP code — 200 on success or the failure code from the underlying issue. Batch Bundles always return 200, and per-entry outcomes live in the response Bundle. For the deeper picture, Bundle transactions vs batch: the atomicity boundary covers the split.
The Trap: 200 With an OperationOutcome Warning
Some servers return 200 with an OperationOutcome that has warning-severity issues — the request succeeded, but with warnings that matter. A client that dispatches purely on status code misses those. Parse the body on 200 too when the response type is FHIR-shaped.
Server Drift Is Real
Not every FHIR server maps codes cleanly. Some return 500 where 400 fits. Some return 400 where 422 fits. That drift is why the OperationOutcome parse matters — it is the layer that survives inconsistent status coding. Do not build your client to rely only on codes when the body is available and cheap to inspect.
For the wider verb picture where these status codes land, the FHIR REST verbs that actually differ from vanilla REST is the entry. For where all of this converges into client shape, designing a FHIR client that survives the tricky verbs is the follow-up.
Sources
- HL7 canonical HTTP status code chapter for FHIR interactions - HL7 canonical HTTP status code chapter for FHIR interactions