FHIR’s RESTful API uses standard HTTP methods for resource manipulation. Unlike traditional REST APIs, FHIR adds standardized versioning, conditional operations, and strict requirements around headers and response formats.
The Five Core Operations
FHIR defines five fundamental operations for resource lifecycle management:
| Operation | HTTP Method | Purpose | Creates Version |
|---|
| Create | POST | Create new resource with server-assigned ID | Yes |
| Read | GET | Retrieve current version of a resource | No |
| Update | PUT | Replace entire resource (can create if allowed) | Yes |
| Patch | PATCH | Modify part of a resource | Yes |
| Delete | DELETE | Mark resource as deleted | Yes |
URL Structure
FHIR uses predictable, hierarchical URLs:
POST /fhir/{resourceType} # Create with server-assigned ID
GET /fhir/{resourceType}/{id} # Read current version
PUT /fhir/{resourceType}/{id} # Update (replace entirely)
PATCH /fhir/{resourceType}/{id} # Partial update
DELETE /fhir/{resourceType}/{id} # Delete
Why PUT for Updates?
FHIR uses PUT (not PATCH) for updates because updates are total replacements:
- You must send the complete resource (not just changed fields)
- Missing fields are removed (not preserved)
- This ensures the server’s state matches what you sent exactly
Update vs. Create: Many servers allow PUT to create resources if they
don’t exist (“update-as-create”). This lets clients assign their own IDs.
Check your server’s CapabilityStatement to see if this is supported.
Versioning and Concurrency
Every write operation creates a new version. FHIR tracks versions using:
meta.versionId - Sequential version identifier (e.g., “1”, “2”, “3”)
meta.lastUpdated - ISO 8601 timestamp of last modification
ETag header - HTTP cache validation (formatted as W/"{versionId}")
Last-Modified header - HTTP-formatted timestamp
Preventing Lost Updates
Use If-Match to implement optimistic concurrency control:
PUT /fhir/Patient/example HTTP/1.1
If-Match: W/"2"
The server will reject the update if the current version isn’t “2”, preventing accidental overwrites.
Conditional Operations
FHIR extends HTTP with conditional operations that target resources by search criteria instead of ID:
Conditional Create
Prevent duplicates by checking if a matching resource exists:
POST /fhir/Patient HTTP/1.1
If-None-Exist: identifier=http://hospital.example/mrn|12345
- If no match: Creates resource, returns
201 Created
- If one match: Returns
200 OK with existing resource
- If multiple matches: Returns
412 Precondition Failed
Conditional Update
Update the resource that matches search criteria:
PUT /fhir/Patient?identifier=http://hospital.example/mrn|12345
- If no match: Creates resource (if server allows), returns
201 Created
- If one match: Updates resource, returns
200 OK
- If multiple matches: Returns
412 Precondition Failed
Conditional Delete
Delete resource(s) matching search criteria:
DELETE /fhir/Patient?identifier=http://hospital.example/mrn|12345
Servers may allow deleting multiple matches or require a single match.
Patch: Partial Updates
Unlike PUT, PATCH modifies only specified fields. FHIR supports two patch formats:
JSON Patch (RFC 6902)
Standard JSON operations:
[
{ "op": "replace", "path": "/name/0/given/0", "value": "Jane" },
{
"op": "add",
"path": "/telecom/0",
"value": { "system": "phone", "value": "555-0100" }
},
{ "op": "remove", "path": "/address/0" }
]
Content-Type: application/json-patch+json
FHIRPath Patch
FHIR-specific format using FHIRPath expressions:
{
"resourceType": "Parameters",
"parameter": [
{
"name": "operation",
"part": [
{ "name": "type", "valueCode": "replace" },
{ "name": "path", "valueString": "Patient.name[0].given[0]" },
{ "name": "value", "valueString": "Jane" }
]
}
]
}
Content-Type: application/fhir+json
After Patching: Some servers drop text (narrative) after a patch to
prevent serving stale human-readable content that no longer matches the
structured data.
Delete Behavior
FHIR delete is idempotent and doesn’t strictly require the resource to exist:
- First delete:
200 OK or 204 No Content
- Subsequent deletes: Still
200 OK or 204 No Content
Soft vs. Hard Delete
- Soft Delete (typical): Resource marked as deleted, still in history
- Hard Delete (rare): Resource completely removed from database
After soft delete:
GET /Patient/123 → 410 Gone
GET /Patient/123/_history/2 → 200 OK (history still accessible)
Response Preferences
Control what the server returns after writes using the Prefer header:
Prefer: return=representation # Return full resource (default)
Prefer: return=minimal # Return headers only (no body)
Prefer: return=OperationOutcome # Return OperationOutcome describing the change
Use return=minimal to reduce bandwidth when you don’t need the response body.
Content Negotiation
Specify format using Accept and Content-Type headers:
| Format | Accept Header | Content-Type Header |
|---|
| JSON | application/fhir+json | application/fhir+json |
| XML | application/fhir+xml | application/fhir+xml |
Use FHIR MIME types: Always use application/fhir+json (not
application/json) to ensure proper FHIR processing.
Common Patterns
Client-Assigned IDs
Use PUT to create resources with your own IDs:
PUT /fhir/Patient/my-patient-123 HTTP/1.1
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"id": "my-patient-123",
"name": [{ "family": "Doe" }]
}
Idempotent Creates
Combine conditional create with client-assigned IDs:
POST /fhir/Patient HTTP/1.1
If-None-Exist: identifier=http://example.org/mrn|12345
First request creates, subsequent requests return existing resource.
Safe Updates
Always use If-Match for updates to prevent race conditions:
GET /fhir/Patient/123 → Note the ETag: W/"5"
- Modify resource locally
PUT /fhir/Patient/123 with If-Match: W/"5"
If someone else updated between steps 1 and 3, you’ll get 412 Precondition Failed.
Quick Reference
| Goal | Method | Example |
|---|
| Create new | POST | POST /fhir/Patient |
| Create with custom ID | PUT | PUT /fhir/Patient/my-id |
| Prevent duplicate | POST + header | If-None-Exist: identifier=... |
| Get resource | GET | GET /fhir/Patient/123 |
| Check existence | HEAD | HEAD /fhir/Patient/123 |
| Replace resource | PUT | PUT /fhir/Patient/123 |
| Safe replace | PUT + header | If-Match: W/"3" |
| Partial update | PATCH | PATCH /fhir/Patient/123 |
| Remove resource | DELETE | DELETE /fhir/Patient/123 |
Next Steps