Skip to main content
FHIR Specification: RESTful API | Create | Update | Delete
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:
OperationHTTP MethodPurposeCreates Version
CreatePOSTCreate new resource with server-assigned IDYes
ReadGETRetrieve current version of a resourceNo
UpdatePUTReplace entire resource (can create if allowed)Yes
PatchPATCHModify part of a resourceYes
DeleteDELETEMark resource as deletedYes

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/123410 Gone
  • GET /Patient/123/_history/2200 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:
FormatAccept HeaderContent-Type Header
JSONapplication/fhir+jsonapplication/fhir+json
XMLapplication/fhir+xmlapplication/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:
  1. GET /fhir/Patient/123 → Note the ETag: W/"5"
  2. Modify resource locally
  3. 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

GoalMethodExample
Create newPOSTPOST /fhir/Patient
Create with custom IDPUTPUT /fhir/Patient/my-id
Prevent duplicatePOST + headerIf-None-Exist: identifier=...
Get resourceGETGET /fhir/Patient/123
Check existenceHEADHEAD /fhir/Patient/123
Replace resourcePUTPUT /fhir/Patient/123
Safe replacePUT + headerIf-Match: W/"3"
Partial updatePATCHPATCH /fhir/Patient/123
Remove resourceDELETEDELETE /fhir/Patient/123

Next Steps