FHIR Developer Skill Quick Reference HTTP Status Codes Code When to Use 200 OK Successful read, update, or search 201 Created Successful create (include Location header) 204 No Content Successful delete 400 Bad Request Malformed JSON, wrong resourceType 401 Unauthorized Missing, expired, revoked, or malformed token (RFC 6750) 403 Forbidden Valid token but insufficient scopes 404 Not Found Resource doesn't exist 412 Precondition Failed If-Match ETag mismatch (NOT 400!) 422 Unprocessable Entity Missing required fields, invalid enum values, business rule violations Required Fields by Resource (FHIR R4) Resource Required Fields Everything Else Patient (none) All optional Observation status, code Optional Encounter status, class Optional (including subject, period) Condition subject Optional (including code, clinicalStatus) MedicationRequest status, intent, medication[x], subject Optional Medication (none) All optional Bundle type Optional Required vs Optional Fields (CRITICAL)
Only validate fields with cardinality starting with "1" as required.
Cardinality Required? 0..1, 0.. NO 1..1, 1.. YES
Common mistake: Making subject or period required on Encounter. They are 0..1 (optional).
Value Sets (Enum Values)
Invalid enum values must return 422 Unprocessable Entity.
Patient.gender
male | female | other | unknown
Observation.status
registered | preliminary | final | amended | corrected | cancelled | entered-in-error | unknown
Encounter.status
planned | arrived | triaged | in-progress | onleave | finished | cancelled | entered-in-error | unknown
Encounter.class (Common Codes) Code Display Use AMB ambulatory Outpatient visits IMP inpatient encounter Hospital admissions EMER emergency Emergency department VR virtual Telehealth Condition.clinicalStatus
active | recurrence | relapse | inactive | remission | resolved
Condition.verificationStatus
unconfirmed | provisional | differential | confirmed | refuted | entered-in-error
MedicationRequest.status
active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown
MedicationRequest.intent
proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option
Bundle.type
document | message | transaction | transaction-response | batch | batch-response | history | searchset | collection
Validation Pattern
Python/FastAPI:
from fastapi import FastAPI from fastapi.responses import JSONResponse
app = FastAPI()
def operation_outcome(severity: str, code: str, diagnostics: str): return { "resourceType": "OperationOutcome", "issue": [{"severity": severity, "code": code, "diagnostics": diagnostics}] }
VALID_OBS_STATUS = {"registered", "preliminary", "final", "amended", "corrected", "cancelled", "entered-in-error", "unknown"}
@app.post("/Observation", status_code=201) async def create_observation(data: dict): if not data.get("status"): return JSONResponse(status_code=422, content=operation_outcome( "error", "required", "Observation.status is required" ), media_type="application/fhir+json")
if data["status"] not in VALID_OBS_STATUS:
return JSONResponse(status_code=422, content=operation_outcome(
"error", "value", f"Invalid status '{data['status']}'"
), media_type="application/fhir+json")
# ... create resource
TypeScript/Express:
const VALID_OBS_STATUS = new Set(['registered', 'preliminary', 'final', 'amended', 'corrected', 'cancelled', 'entered-in-error', 'unknown']);
app.post('/Observation', (req, res) => {
if (!req.body.status) {
return res.status(422).contentType('application/fhir+json')
.json(operationOutcome('error', 'required', 'Observation.status is required'));
}
if (!VALID_OBS_STATUS.has(req.body.status)) {
return res.status(422).contentType('application/fhir+json')
.json(operationOutcome('error', 'value', Invalid status '${req.body.status}'));
}
// ... create resource
});
Pydantic v2 Models (use Literal, not const=True):
from typing import Literal from pydantic import BaseModel
class Patient(BaseModel): resourceType: Literal["Patient"] = "Patient" id: str | None = None gender: Literal["male", "female", "other", "unknown"] | None = None
Coding Systems (URLs) System URL LOINC http://loinc.org SNOMED CT http://snomed.info/sct RxNorm http://www.nlm.nih.gov/research/umls/rxnorm ICD-10 http://hl7.org/fhir/sid/icd-10 v3-ActCode http://terminology.hl7.org/CodeSystem/v3-ActCode Observation Category http://terminology.hl7.org/CodeSystem/observation-category Condition Clinical http://terminology.hl7.org/CodeSystem/condition-clinical Condition Ver Status http://terminology.hl7.org/CodeSystem/condition-ver-status Common LOINC Codes (Vital Signs) Code Description 8867-4 Heart rate 8480-6 Systolic blood pressure 8462-4 Diastolic blood pressure 8310-5 Body temperature 2708-6 Oxygen saturation (SpO2) Data Type Patterns Coding (direct) vs CodeableConcept (wrapped)
Coding - Used by Encounter.class:
{"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB"}
CodeableConcept - Used by Observation.code, Condition.code:
{"coding": [{"system": "http://loinc.org", "code": "8480-6"}], "text": "Systolic BP"}
Reference
Identifier
Common Mistakes Mistake Correct Approach Making subject or period required on Encounter Both are 0..1 (optional). Only status and class are required Using CodeableConcept for Encounter.class class uses Coding directly: {"system": "...", "code": "AMB"} Returning 400 for ETag mismatch Use 412 Precondition Failed for If-Match failures Returning 400 for invalid enum values Use 422 Unprocessable Entity for validation errors Forgetting Content-Type header Always set Content-Type: application/fhir+json Missing Location header on create Return Location: /Patient/{id} with 201 Created Resource Structures
For complete JSON examples of all resources, see references/resource-examples.md.
Quick reference for error responses:
{ "resourceType": "OperationOutcome", "issue": [{"severity": "error", "code": "not-found", "diagnostics": "Patient/123 not found"}] }
RESTful Endpoints POST /[ResourceType] # Create (returns 201 + Location header) GET /[ResourceType]/[id] # Read PUT /[ResourceType]/[id] # Update DELETE /[ResourceType]/[id] # Delete (returns 204) GET /[ResourceType]?param=value # Search (returns Bundle) GET /metadata # CapabilityStatement POST / # Bundle transaction/batch
Conditional Operations
If-Match (optimistic locking):
Client sends: If-Match: W/"1" Mismatch returns 412 Precondition Failed
If-None-Exist (conditional create):
Client sends: If-None-Exist: identifier=http://mrn|12345 Match exists: return existing (200) No match: create new (201) Reference Files
For detailed guidance, see:
Resource Examples: Complete JSON structures for Patient, Observation, Encounter, Condition, MedicationRequest, OperationOutcome, CapabilityStatement SMART on FHIR Authorization: OAuth flows, scope syntax (v1/v2), backend services, scope enforcement Pagination: Search result pagination, _count/_offset parameters, link relations Bundle Operations: Transaction vs batch semantics, atomicity, processing order Implementation Checklist Set Content-Type: application/fhir+json on all responses Return meta.versionId and meta.lastUpdated on resources Return Location header on create: /Patient/{id} Return ETag header: W/"{versionId}" Use OperationOutcome for all error responses Validate required fields → 422 for missing Validate enum values → 422 for invalid Search returns Bundle with type: "searchset" Quick Start Script
To scaffold a new FHIR API project with correct Pydantic v2 patterns:
python scripts/setup_fhir_project.py my_fhir_api
Creates a FastAPI project with correct models, OperationOutcome helpers, and Patient CRUD endpoints.