Back to blog
Security & Complianceintermediate

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.

LearnixoApril 17, 20267 min read
FHIRHL7HealthcareFHIR R4Clinical Data.NETHealthcare API
Share:𝕏
FHIR

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.

JSON
{
  "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 history

Standard 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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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

JSON
{
  "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

HTTP
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=active

Search 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:

JSON
{
  "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

JSON
{
  "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:

Bash
dotnet add package Hl7.Fhir.R4
dotnet add package Hl7.Fhir.R4.Specification
C#
using 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

C#
// 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

Bash
pip install fhirclient
Python
from 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?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.