Loading...
Loading...
FHIR API development guide for building healthcare endpoints. Use when: (1) Creating FHIR REST endpoints (Patient, Observation, Encounter, Condition, MedicationRequest), (2) Validating FHIR resources and returning proper HTTP status codes and error responses, (3) Implementing SMART on FHIR authorization and OAuth scopes, (4) Working with Bundles, transactions, batch operations, or search pagination. Covers FHIR R4 resource structures, required fields, value sets (status codes, gender, intent), coding systems (LOINC, SNOMED, RxNorm, ICD-10), and OperationOutcome error handling.
npx skill4agent add anthropics/healthcare fhir-developer-skill| Code | When to Use |
|---|---|
| Successful read, update, or search |
| Successful create (include |
| Successful delete |
| Malformed JSON, wrong resourceType |
| Missing, expired, revoked, or malformed token (RFC 6750) |
| Valid token but insufficient scopes |
| Resource doesn't exist |
| If-Match ETag mismatch (NOT 400!) |
| Missing required fields, invalid enum values, business rule violations |
| Resource | Required Fields | Everything Else |
|---|---|---|
| Patient | (none) | All optional |
| Observation | | Optional |
| Encounter | | Optional (including |
| Condition | | Optional (including |
| MedicationRequest | | Optional |
| Medication | (none) | All optional |
| Bundle | | Optional |
| Cardinality | Required? |
|---|---|
| NO |
| YES |
subjectperiod422 Unprocessable Entitymale | female | other | unknownregistered | preliminary | final | amended | corrected | cancelled | entered-in-error | unknownplanned | arrived | triaged | in-progress | onleave | finished | cancelled | entered-in-error | unknown| Code | Display | Use |
|---|---|---|
| ambulatory | Outpatient visits |
| inpatient encounter | Hospital admissions |
| emergency | Emergency department |
| virtual | Telehealth |
active | recurrence | relapse | inactive | remission | resolvedunconfirmed | provisional | differential | confirmed | refuted | entered-in-erroractive | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknownproposal | plan | order | original-order | reflex-order | filler-order | instance-order | optiondocument | message | transaction | transaction-response | batch | batch-response | history | searchset | collectionfrom 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 resourceconst 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
});Literalconst=Truefrom 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| System | URL |
|---|---|
| LOINC | |
| SNOMED CT | |
| RxNorm | |
| ICD-10 | |
| v3-ActCode | |
| Observation Category | |
| Condition Clinical | |
| Condition Ver Status | |
| Code | Description |
|---|---|
| Heart rate |
| Systolic blood pressure |
| Diastolic blood pressure |
| Body temperature |
| Oxygen saturation (SpO2) |
Encounter.class{"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB"}Observation.codeCondition.code{"coding": [{"system": "http://loinc.org", "code": "8480-6"}], "text": "Systolic BP"}{"reference": "Patient/123", "display": "John Smith"}{"system": "http://hospital.example.org/mrn", "value": "12345"}| Mistake | Correct Approach |
|---|---|
Making | Both are 0..1 (optional). Only |
Using CodeableConcept for | |
| Returning 400 for ETag mismatch | Use |
| Returning 400 for invalid enum values | Use |
| Forgetting Content-Type header | Always set |
| Missing Location header on create | Return |
{
"resourceType": "OperationOutcome",
"issue": [{"severity": "error", "code": "not-found", "diagnostics": "Patient/123 not found"}]
}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/batchIf-Match: W/"1"412 Precondition FailedIf-None-Exist: identifier=http://mrn|12345_count_offsetContent-Type: application/fhir+jsonmeta.versionIdmeta.lastUpdatedLocation/Patient/{id}ETagW/"{versionId}"type: "searchset"python scripts/setup_fhir_project.py my_fhir_api