FHIR R4: Modeling Clinical Data
Learn FHIR R4 — the international standard for healthcare data exchange. Model Patient, Appointment, Observation, Condition, and Practitioner resources. Query with RESTful FHIR APIs in .NET and Python.
What is FHIR?
FHIR (Fast Healthcare Interoperability Resources, pronounced "fire") is the international standard for healthcare data exchange, published by HL7 International. FHIR R4 (Release 4) is the current production-stable version, required by US federal regulations (21st Century Cures Act) for healthcare APIs.
Why FHIR matters:
- Replaces legacy HL7 v2 (pipe-delimited text messages) and CDA (XML documents)
- RESTful HTTP API with JSON/XML — works with any HTTP client
- Required for US healthcare interoperability mandates
- Used by Epic, Cerner, Apple Health, Google Health, and all major EHR systems
Core Concepts
Resources
FHIR represents clinical data as resources — standardised JSON objects. Each resource has a type, an ID, and typed fields.
{
"resourceType": "Patient",
"id": "PAT-001",
"meta": {
"versionId": "1",
"lastUpdated": "2026-04-17T09:00:00Z"
},
"identifier": [
{
"use": "official",
"system": "http://hospital.example/patients",
"value": "MRN-12345"
}
],
"active": true,
"name": [
{
"use": "official",
"family": "Johnson",
"given": ["Alice", "Marie"]
}
],
"gender": "female",
"birthDate": "1985-06-15",
"telecom": [
{ "system": "phone", "value": "555-0100", "use": "mobile" },
{ "system": "email", "value": "alice@example.com" }
],
"address": [
{
"use": "home",
"line": ["123 Main St"],
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"country": "US"
}
]
}The FHIR RESTful API
FHIR follows REST conventions on HTTP:
GET /Patient/{id} → Read one patient
POST /Patient → Create patient (server assigns ID)
PUT /Patient/{id} → Update patient
DELETE /Patient/{id} → Delete patient
GET /Patient?name=Johnson → Search patients
GET /Patient/{id}/_history → Version historyStandard response codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 422 Unprocessable Entity.
Key FHIR R4 Resources
Patient
The central resource — represents a person receiving care.
{
"resourceType": "Patient",
"id": "PAT-001",
"identifier": [{
"system": "http://hl7.org/fhir/sid/us-ssn",
"value": "123-45-6789"
}],
"name": [{ "family": "Johnson", "given": ["Alice"] }],
"gender": "female",
"birthDate": "1985-06-15",
"generalPractitioner": [{ "reference": "Practitioner/DOC-42" }],
"managingOrganization": { "reference": "Organization/CLINIC-1" }
}Appointment
An appointment between a patient and a practitioner at a location.
{
"resourceType": "Appointment",
"id": "APT-001",
"status": "booked",
"serviceType": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "11429006",
"display": "Consultation"
}]
}],
"start": "2026-04-20T10:00:00+00:00",
"end": "2026-04-20T10:30:00+00:00",
"participant": [
{
"actor": { "reference": "Patient/PAT-001", "display": "Alice Johnson" },
"status": "accepted"
},
{
"actor": { "reference": "Practitioner/DOC-42", "display": "Dr. Smith" },
"status": "accepted"
}
],
"comment": "Annual wellness check"
}Appointment status values: proposed, pending, booked, arrived, fulfilled, cancelled, noshow, entered-in-error
Observation
A measurement or assessment — vital signs, lab results, survey answers.
{
"resourceType": "Observation",
"id": "OBS-001",
"status": "final",
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}]
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}]
},
"subject": { "reference": "Patient/PAT-001" },
"effectiveDateTime": "2026-04-17T09:15:00Z",
"valueQuantity": {
"value": 120,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
}
}Condition
A clinical condition, diagnosis, or problem.
{
"resourceType": "Condition",
"id": "COND-001",
"clinicalStatus": {
"coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" }]
},
"verificationStatus": {
"coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "confirmed" }]
},
"code": {
"coding": [{
"system": "http://snomed.info/sct",
"code": "44054006",
"display": "Diabetes mellitus type 2"
}]
},
"subject": { "reference": "Patient/PAT-001" },
"onsetDateTime": "2020-03-01",
"recordedDate": "2026-04-17"
}Practitioner & PractitionerRole
{
"resourceType": "Practitioner",
"id": "DOC-42",
"name": [{ "family": "Smith", "given": ["Robert"], "prefix": ["Dr."] }],
"qualification": [{
"code": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v2-0360",
"code": "MD",
"display": "Doctor of Medicine"
}]
}
}]
}FHIR Coding Systems
FHIR uses standardised medical terminologies to make data interoperable:
| System | OID | Used For | |--------|-----|---------| | SNOMED CT | http://snomed.info/sct | Diagnoses, findings, procedures | | LOINC | http://loinc.org | Lab tests, vital signs codes | | RxNorm | http://www.nlm.nih.gov/research/umls/rxnorm | Medications | | ICD-10 | http://hl7.org/fhir/sid/icd-10 | Billing diagnoses | | CPT | http://www.ama-assn.org/go/cpt | Billing procedures | | UCUM | http://unitsofmeasure.org | Units of measure |
Querying FHIR APIs
Search Parameters
GET /Patient?family=Johnson&birthdate=1985-06-15
GET /Patient?identifier=MRN-12345
GET /Appointment?patient=Patient/PAT-001&date=ge2026-04-01
GET /Observation?subject=Patient/PAT-001&code=8480-6&_sort=-date&_count=10
GET /Condition?patient=Patient/PAT-001&clinical-status=activeSearch modifiers:
eq=,ne≠,gt>,lt<,ge≥,le≤sa(starts-after),eb(ends-before)_count,_sort,_include,_revinclude
Bundle: Multi-Resource Response
FHIR search returns a Bundle:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 2,
"entry": [
{
"fullUrl": "https://fhir.example.com/Patient/PAT-001",
"resource": { "resourceType": "Patient", "id": "PAT-001", ... },
"search": { "mode": "match" }
},
{
"fullUrl": "https://fhir.example.com/Patient/PAT-002",
"resource": { "resourceType": "Patient", "id": "PAT-002", ... },
"search": { "mode": "match" }
}
]
}Transaction Bundle: Multiple Operations Atomically
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"request": { "method": "POST", "url": "Patient" },
"resource": { "resourceType": "Patient", ... }
},
{
"request": { "method": "POST", "url": "Appointment" },
"resource": { "resourceType": "Appointment", ... }
}
]
}Working with FHIR in .NET
The Firely .NET SDK (Hl7.Fhir.R4) is the standard library for FHIR in .NET:
dotnet add package Hl7.Fhir.R4
dotnet add package Hl7.Fhir.R4.Specificationusing Hl7.Fhir.Model;
using Hl7.Fhir.Rest;
using Hl7.Fhir.Serialization;
// Create FHIR client
var settings = new FhirClientSettings
{
PreferredFormat = ResourceFormat.Json,
PreferredReturn = Prefer.ReturnRepresentation,
};
var client = new FhirClient("https://r4.smarthealthit.org", settings);
// Read a patient
var patient = await client.ReadAsync<Patient>("Patient/PAT-001");
Console.WriteLine($"Patient: {patient.Name.First().Family}");
// Create a patient
var newPatient = new Patient
{
Name = [new HumanName { Family = "Johnson", Given = ["Alice"] }],
Gender = AdministrativeGender.Female,
BirthDate = "1985-06-15",
Identifier =
[
new Identifier("http://hospital.example/patients", "MRN-12345")
]
};
var created = await client.CreateAsync(newPatient);
Console.WriteLine($"Created: {created.Id}");
// Search patients
var searchParams = new SearchParams()
.Where("family=Johnson")
.Where("birthdate=1985-06-15")
.LimitTo(10)
.SortBy("-birthdate");
var bundle = await client.SearchAsync<Patient>(searchParams);
foreach (var entry in bundle.Entry)
{
var p = (Patient)entry.Resource;
Console.WriteLine($" {p.Id}: {p.Name.First().Family}");
}
// Serialize to JSON
var serializer = new FhirJsonSerializer(new SerializerSettings { Pretty = true });
string json = serializer.SerializeToString(patient);Mapping Between Domain Model and FHIR
// Domain model → FHIR Patient resource
public static class FhirMapper
{
public static Patient ToFhirPatient(PatientEntity entity)
{
return new Patient
{
Id = entity.FhirId ?? $"PAT-{entity.Id}",
Identifier =
[
new Identifier
{
System = "http://yourapp.com/patients",
Value = entity.MedicalRecordNumber,
}
],
Name =
[
new HumanName
{
Family = entity.LastName,
Given = [entity.FirstName],
Use = HumanName.NameUse.Official,
}
],
Gender = entity.Gender switch
{
"M" => AdministrativeGender.Male,
"F" => AdministrativeGender.Female,
_ => AdministrativeGender.Unknown,
},
BirthDate = entity.DateOfBirth.ToString("yyyy-MM-dd"),
Telecom = [
new ContactPoint
{
System = ContactPoint.ContactPointSystem.Phone,
Value = entity.Phone,
Use = ContactPoint.ContactPointUse.Mobile,
}
],
};
}
public static PatientEntity FromFhirPatient(Patient fhir)
{
var name = fhir.Name.FirstOrDefault();
return new PatientEntity
{
FhirId = fhir.Id,
FirstName = name?.Given.FirstOrDefault() ?? "",
LastName = name?.Family ?? "",
DateOfBirth = DateTime.Parse(fhir.BirthDate),
Gender = fhir.Gender switch
{
AdministrativeGender.Male => "M",
AdministrativeGender.Female => "F",
_ => "U",
},
MedicalRecordNumber = fhir.Identifier
.FirstOrDefault(i => i.System == "http://yourapp.com/patients")?.Value ?? "",
};
}
}Working with FHIR in Python
pip install fhirclientfrom fhirclient import client
from fhirclient.models import patient, appointment, observation
import json
# Setup
settings = {
'app_id': 'myapp',
'api_base': 'https://r4.smarthealthit.org'
}
smart = client.FHIRClient(settings=settings)
# Read patient
pat = patient.Patient.read('PAT-001', smart.server)
print(f"Patient: {pat.name[0].family}")
# Search appointments
import fhirclient.models.appointment as a
search = a.Appointment.where(struct={
'patient': 'Patient/PAT-001',
'date': 'ge2026-04-01'
})
appointments = search.perform_resources(smart.server)
for appt in appointments:
print(f"Appointment: {appt.start} - {appt.status}")
# Create observation (vital sign)
obs = observation.Observation({
'status': 'final',
'category': [{
'coding': [{
'system': 'http://terminology.hl7.org/CodeSystem/observation-category',
'code': 'vital-signs'
}]
}],
'code': {
'coding': [{
'system': 'http://loinc.org',
'code': '8480-6',
'display': 'Systolic blood pressure'
}]
},
'subject': {'reference': 'Patient/PAT-001'},
'effectiveDateTime': '2026-04-17T09:00:00Z',
'valueQuantity': {
'value': 120,
'unit': 'mmHg',
'system': 'http://unitsofmeasure.org',
'code': 'mm[Hg]'
}
})
obs.create(smart.server)FHIR SMART on FHIR: OAuth 2.0 Integration
SMART on FHIR adds OAuth 2.0 to FHIR for patient-facing apps:
Patient opens app
│
▼
App redirects to EHR's authorization server
│
▼
Patient authenticates + consents to scopes:
- patient/Patient.read
- patient/Observation.read
│
▼
App receives access token
│
▼
App calls FHIR API with Bearer token:
GET /Patient?_id=current
Authorization: Bearer {token}Summary
| Resource | Models |
|----------|--------|
| Patient | Demographics, identifiers, contacts |
| Appointment | Scheduled visits, status, participants |
| Observation | Vitals, lab results, assessments |
| Condition | Diagnoses, problems |
| Practitioner | Clinician details |
| Organization | Hospital, clinic details |
| MedicationRequest | Prescriptions |
| AllergyIntolerance | Allergies, adverse reactions |
Next up: Building with Oystehr — using the headless EHR platform to build patient-facing healthcare applications.
Enjoyed this article?
Explore the Security & Compliance learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.