Offsiteteam
Health Tech
October 27, 2026

FHIR Data Exchange: Architecture, Service Discovery, and Hands-on Setup

FHIR - modern healthcare data exchange protocol

FHIR (pronounced “fire”) stands for Fast Healthcare Interoperability Resources.

It was developed by the HL7 International organization as a standard for exchanging healthcare information electronically.

Let's bring a bit of history.

Historically, medical information was scattered around hospitals, ambulances and other healthcare points. If a patient needs to move to an alternative general practitioner he needs to take his/her personal health records (EHR) on paper and physically brings them to another healthcare provider. Again, if someone was brought to an emergency room, to get access to his previous health information was almost impossible.

Then electronic databases emerged. Typically they were developed by different software companies for different hospitals and again, moving information between them was a nightmare.

So, the need for a system that would make different medical information systems communicate with one another was obvious.

The first attempts to solve this problem was made in 1990s by HL7 organization.

HL7 stands for Health Level Seven, a set of international technical standards that allow different healthcare software systems to share, integrate, and exchange patient information.

First came the HL7 Version 2 messaging standard. It established a practical way for healthcare applications to exchange events such as patient admissions, discharges, transfers, and laboratory results.

In the 2000s the HL7 Version 3 was developed.

HL7 began developing FHIR in the 2011. Release 3 appeared in 2017, and FHIR Release 4 (R4) was published in the 2019.

but FHIR became a central building block for healthcare APIs in the 2020s


FHIR from a technical point of view.

Data

FHIR is built around several entities.

  • Encounter, it is a visiting doctor event, all started from there.
  • You can link other entities to Encounter:
    • Condition - a diagnosis
    • Observation - instrumental measurements and doctor's observation
    • MedicationRequest - a drug prescription
    • DocumentReference - any written recommendations and other documents

Data exchange format

To exchange data, FHIR usually uses the JSON format but also natively supports XML, NDJSON and even HTML.

To select the format FHIR uses standard HTTP content negotiation (FHIR server supports all options internally)

GET /Patient/123
Accept: application/fhir+json

or

GET /Patient/123
Accept: application/fhir+xml

# When creating or updating a resource, the Content-Type header indicates the format of the request body:
Content-Type: application/fhir+json

Clinics endpoints lookup

Before requesting anything one needs to know the URL of clinic's end-point.

In FHIR, there are actually two different kinds of lookups:

  1. Finding a clinic or healthcare organization (a directory/discovery).
  2. Finding the technical API endpoint that clinic exposes (a service discovery).

Discovery

For example, for EPIC the endpoint is https://open.epic.com/MyApps/Endpoints.

Usually, one will be provided with an endpoint when contacting EHR provider or you can easily google it.

In general, let's assume we have it like this, https://directory.example.org/fhir

Many large EHR providers work with multiple clinics and you can get User-access Brands and Endpoints, which is a Bundle with a list of resources available.

Service discovery

Having endpoint you can ask

GET /HealthcareService?name=cardiology
Accept: application/fhir+json

The directory returns a FHIR Bundle containing matching services.

{
  "resourceType": "Bundle",
  "type": "searchset",
  "entry": [
    {
      "resource": {
        "resourceType": "HealthcareService",
        "id": "cardiology-01",
        "name": "Cardiology",
        "providedBy": {
          "reference": "Organization/clinic-01"
        },
        "location": [
          {
            "reference": "Location/site-01"
          }
        ],
        "endpoint": [
          {
            "reference": "Endpoint/api-01"
          }
        ]
      }
    }
  ]
}

General flow

The flow looks like this:

get a visit information: Encounter

GET /Encounter/enc-001
Accept: application/fhir+json

Response: Encounter

{
  "resourceType": "Encounter",
  "id": "enc-001",
  "status": "finished",
  "class": {
    "system": "https://terminology.hl7.org/CodeSystem/v3-ActCode",
    "code": "AMB",
    "display": "ambulatory"
  },
  "subject": {
    "reference": "Patient/pat-001"
  },
  "period": {
    "start": "2026-10-20T09:00:00Z",
    "end": "2026-10-20T09:30:00Z"
  },
  "reasonReference": [
    {
      "reference": "Condition/cond-001"
    }
  ],
  "diagnosis": [
    {
      "condition": {
        "reference": "Condition/cond-001"
      }
    }
  ]
}

Get a pressure reading: Observation

{
  "resourceType": "Observation",
  "id": "obs-001",
  "status": "final",
  "category": [
    {
      "coding": [
        {
          "system": "https://terminology.hl7.org/CodeSystem/observation-category",
          "code": "vital-signs"
        }
      ]
    }
  ],
  "code": {
    "text": "Systolic blood pressure"
  },
  "subject": {
    "reference": "Patient/pat-001"
  },
  "encounter": {
    "reference": "Encounter/enc-001"
  },
  "valueQuantity": {
    "value": 120,
    "unit": "mmHg",
    "system": "https://unitsofmeasure.org",
    "code": "mm[Hg]"
  }
}

FHIR docker and test data

To test your software's FHIR integration you can use either sandboxes provided by EHR providers or a test FHIR server in docker

# Local FHIR server, so fhir/client.py and fhir/flatten.py can be written against real
# FHIR payloads before credentials for the production server arrive.
#
#   docker compose -f docker/docker-compose-fhir.yaml up -d
#   curl -s http://localhost:8080/fhir/metadata | jq '.rest[0].security'
#
# Then put FHIR_BASE_URL=http://localhost:8080/fhir in .env and leave FHIR_TOKEN empty.
# Uses the embedded H2 database: data is lost on `down`, so keep seeding repeatable.

services:
  fhir:
    image: hapiproject/hapi:latest
    ports:
      - "8080:8080"
    environment:
      HAPI_FHIR_FHIR_VERSION: R4
      HAPI_FHIR_ALLOW_EXTERNAL_REFERENCES: "true"
      HAPI_FHIR_BULK_EXPORT_ENABLED: "true"
      # small page size locally, so client.py is forced to follow Bundle.link next
      HAPI_FHIR_DEFAULT_PAGE_SIZE: "20"
      HAPI_FHIR_MAX_PAGE_SIZE: "200"

On top of that you can import a larger dataset from Synthea.

Ready to use Health Tech
to improve your business?
Fill out the form below to tell us about your project.
We'll contact you promptly to discuss your needs.
We received your message!
Thank you!