Skip to main content

HealthPass FHIR API Guide (Alpha)

This document describes the FHIR R4 API endpoints required for HealthPass integration. For detailed resource schemas and data models, see fhirdata.md.

Alpha Release

This API is still in alpha and subject to change. Features, endpoints, and data structures may be modified or removed in future releases.


Table of Contents

  1. Overview
  2. Authentication
  3. Patient Endpoints
  4. Observation Endpoints
  5. Condition Endpoints
  6. MedicationRequest Endpoints
  7. Appointment Endpoints
  8. Practitioner Endpoints
  9. RelatedPerson Endpoints
  10. Batch Operations
  11. Common Patterns

Overview

Base URL

https://{your-fhir-server}/fhir/R4

Content Type

All requests and responses use:

Content-Type: application/fhir+json

Response Format

Search operations return a FHIR Bundle:

{
"resourceType": "Bundle",
"type": "searchset",
"total": 42,
"entry": [
{
"fullUrl": "https://server/fhir/R4/Patient/patient-123",
"resource": { ... },
"search": { "mode": "match" }
}
]
}

Authentication

The API supports two authentication methods:

Bearer Token

Authorization: Bearer <access_token>

OAuth 2.0 Client Credentials

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=<id>&client_secret=<secret>

Patient Endpoints

Search Patients

Find patients by identifier, name, or phone number.

GET /Patient?identifier={value}
GET /Patient?name={value}
GET /Patient?telecom={phone_number}

Parameters:

ParameterDescriptionExample
identifierNational ID or MRN9001015009087
namePartial name matchSmith
telecomPhone number+27821234567
_countMax results (default 50)20

Example:

curl -X GET "https://server/fhir/R4/Patient?telecom=+27821234567" \
-H "Authorization: Bearer <token>" \
-H "Accept: application/fhir+json"

Read Patient by ID

GET /Patient/{id}

Example:

curl -X GET "https://server/fhir/R4/Patient/patient-123" \
-H "Authorization: Bearer <token>"

Create Patient

POST /Patient

Conditional Create (Deduplication):

Use If-None-Exist header to prevent duplicate patients:

POST /Patient
If-None-Exist: telecom=+27821234567
  • Returns 201 Created if new patient created
  • Returns 200 OK if existing patient matched

Example:

curl -X POST "https://server/fhir/R4/Patient" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-H "If-None-Exist: telecom=+27821234567" \
-d '{
"resourceType": "Patient",
"active": true,
"name": [{"family": "Smith", "given": ["John"]}],
"telecom": [{"system": "phone", "value": "+27821234567", "use": "mobile"}],
"gender": "male",
"birthDate": "1990-01-01"
}'

Update Patient

PUT /Patient/{id}

Observation Endpoints

Search Observations

Find observations for a patient, optionally filtered by code.

GET /Observation?patient=Patient/{id}
GET /Observation?patient=Patient/{id}&code={loinc_codes}
GET /Observation?patient=Patient/{id}&category={category}

Parameters:

ParameterDescriptionExample
patientPatient reference (required)Patient/patient-123
codeLOINC/SNOMED codes (comma-separated)8310-5,9279-1
categoryObservation categoryvital-signs
_sortSort order-_lastUpdated
_countMax results50

Example - Get vital signs:

curl -X GET "https://server/fhir/R4/Observation?patient=Patient/patient-123&category=vital-signs&_sort=-_lastUpdated" \
-H "Authorization: Bearer <token>"

Example - Get specific observations:

# Get temperature and respiratory rate
curl -X GET "https://server/fhir/R4/Observation?patient=Patient/patient-123&code=8310-5,9279-1" \
-H "Authorization: Bearer <token>"

Create Observation

POST /Observation

Example - Record temperature:

curl -X POST "https://server/fhir/R4/Observation" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Observation",
"status": "final",
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs"
}]
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "8310-5",
"display": "Body temperature"
}]
},
"subject": {"reference": "Patient/patient-123"},
"effectiveDateTime": "2024-01-15T10:30:00Z",
"valueQuantity": {
"value": 37.2,
"unit": "Cel",
"system": "http://unitsofmeasure.org",
"code": "Cel"
}
}'

Condition Endpoints

Search Conditions

GET /Condition?patient=Patient/{id}
GET /Condition?patient=Patient/{id}&clinical-status={status}

Parameters:

ParameterDescriptionExample
patientPatient reference (required)Patient/patient-123
clinical-statusFilter by statusactive
_sortSort order-recordedDate

Example:

curl -X GET "https://server/fhir/R4/Condition?patient=Patient/patient-123&clinical-status=active" \
-H "Authorization: Bearer <token>"

Create Condition

POST /Condition

Example - Record pneumonia diagnosis:

curl -X POST "https://server/fhir/R4/Condition" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Condition",
"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": "233604007", "display": "Pneumonia"},
{"system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "J18.9"}
]
},
"subject": {"reference": "Patient/patient-123"},
"recordedDate": "2024-01-15T10:30:00Z"
}'

MedicationRequest Endpoints

Search MedicationRequests

GET /MedicationRequest?patient=Patient/{id}
GET /MedicationRequest?patient=Patient/{id}&status={status}

Parameters:

ParameterDescriptionExample
patientPatient reference (required)Patient/patient-123
statusPrescription statusactive
_sortSort order-authoredOn

Create MedicationRequest

POST /MedicationRequest

Example - Prescribe amoxicillin:

curl -X POST "https://server/fhir/R4/MedicationRequest" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "MedicationRequest",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": [{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "308182",
"display": "Amoxicillin 500 MG Oral Capsule"
}]
},
"subject": {"reference": "Patient/patient-123"},
"authoredOn": "2024-01-15T10:30:00Z",
"requester": {"reference": "Practitioner/practitioner-456"},
"dosageInstruction": [{
"sequence": 1,
"text": "Take 1 capsule (500mg) by mouth three times a day for 7 days"
}]
}'

Appointment Endpoints

Search Appointments

GET /Appointment?patient=Patient/{id}
GET /Appointment?patient=Patient/{id}&status={status}
GET /Appointment?patient=Patient/{id}&date=ge{date}

Parameters:

ParameterDescriptionExample
patientPatient referencePatient/patient-123
actorAny participant (patient, practitioner, location)Practitioner/prac-456
statusAppointment statusbooked
dateDate filter (supports ge, le, gt, lt)ge2024-01-01
_sortSort order-start

Example - Get upcoming appointments:

curl -X GET "https://server/fhir/R4/Appointment?patient=Patient/patient-123&status=booked&date=ge2024-01-15&_sort=start" \
-H "Authorization: Bearer <token>"

Create Appointment

POST /Appointment

Example:

curl -X POST "https://server/fhir/R4/Appointment" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Appointment",
"status": "booked",
"appointmentType": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v2-0276",
"code": "CONSULT"
}]
},
"description": "Follow-up consultation",
"start": "2024-01-20T09:00:00Z",
"end": "2024-01-20T09:30:00Z",
"participant": [
{"actor": {"reference": "Patient/patient-123"}, "status": "accepted"},
{"actor": {"reference": "Practitioner/practitioner-456"}, "status": "accepted"},
{"actor": {"reference": "Location/location-city-center"}, "status": "accepted"}
]
}'

Update Appointment Status

PUT /Appointment/{id}

Used for cancellation, check-in, or rescheduling.


Practitioner Endpoints

Search Practitioners

GET /Practitioner?name:contains={name}
GET /Practitioner?specialty:text={specialty}

Parameters:

ParameterDescriptionExample
name:containsPartial name matchSmith
specialty:textSpecialty text searchCardiology
_countMax results10
_sortSort ordername

Search by Location (via PractitionerRole)

GET /PractitionerRole?location=Location/{id}&_include=PractitionerRole:practitioner

Example:

curl -X GET "https://server/fhir/R4/PractitionerRole?location=Location/location-city-center&_include=PractitionerRole:practitioner&_count=10" \
-H "Authorization: Bearer <token>"

RelatedPerson Endpoints

HealthPass uses bidirectional RelatedPerson linking. See fhirdata.md for the full pattern.

Find Children of a Parent

GET /RelatedPerson?patient=Patient/{parent_id}&relationship=CHILD

The identifier field in each result contains the child's Patient ID.

Find Parent of a Child

GET /RelatedPerson?patient=Patient/{child_id}&relationship=PRN

The identifier field in the result contains the parent's Patient ID.

Create Bidirectional Relationship

For a parent-child relationship, create two RelatedPerson resources:

1. Attached to Parent (enables "find my children"):

curl -X POST "https://server/fhir/R4/RelatedPerson" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "RelatedPerson",
"patient": {"reference": "Patient/parent-123"},
"relationship": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode",
"code": "CHILD"
}]
}],
"name": [{"family": "Smith", "given": ["Junior"]}],
"identifier": [{
"system": "http://example.org/fhir/related-person-patient",
"value": "child-456"
}]
}'

2. Attached to Child (enables "find my parent"):

curl -X POST "https://server/fhir/R4/RelatedPerson" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "RelatedPerson",
"patient": {"reference": "Patient/child-456"},
"relationship": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode",
"code": "PRN"
}]
}],
"name": [{"family": "Smith", "given": ["John"]}],
"identifier": [{
"system": "http://example.org/fhir/related-person-patient",
"value": "parent-123"
}]
}'

Batch Operations

Create multiple resources atomically using a transaction Bundle.

POST /
Content-Type: application/fhir+json

Example - Create multiple observations:

curl -X POST "https://server/fhir/R4/" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {"coding": [{"system": "http://loinc.org", "code": "8310-5"}]},
"subject": {"reference": "Patient/patient-123"},
"effectiveDateTime": "2024-01-15T10:30:00Z",
"valueQuantity": {"value": 37.2, "unit": "Cel"}
},
"request": {"method": "POST", "url": "Observation"}
},
{
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {"coding": [{"system": "http://loinc.org", "code": "9279-1"}]},
"subject": {"reference": "Patient/patient-123"},
"effectiveDateTime": "2024-01-15T10:30:00Z",
"valueQuantity": {"value": 18, "unit": "/min"}
},
"request": {"method": "POST", "url": "Observation"}
}
]
}'

Common Patterns

Patient ID Lookup Flow

HealthPass stores the FHIR Patient ID in contact fields. When making API calls:

  1. Get Patient ID from contact: contact.fhir_patient_id
  2. Use in API calls: patient=Patient/{fhir_patient_id}

Deduplication

Patient resources are created with conditional create to prevent duplicates:

POST /Patient
If-None-Exist: telecom=+27821234567

Most Recent Record

For observations, conditions, and medications, use sort to get most recent:

GET /Observation?patient=Patient/{id}&_sort=-_lastUpdated&_count=1

Pagination

Use _count and _offset for pagination:

GET /Patient?name=Smith&_count=20&_offset=40

The Bundle response includes total for the full count.


Error Handling

Errors return an OperationOutcome resource:

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"diagnostics": "Patient/invalid-id not found"
}
]
}
HTTP StatusMeaning
200Success (read/search/conditional create matched)
201Created
400Bad request (invalid resource)
401Unauthorized
404Resource not found
422Unprocessable entity (validation failed)

Quick Reference

OperationEndpointMethod
Search patients/Patient?{params}GET
Read patient/Patient/{id}GET
Create patient/PatientPOST
Update patient/Patient/{id}PUT
Search observations/Observation?patient=Patient/{id}GET
Create observation/ObservationPOST
Search conditions/Condition?patient=Patient/{id}GET
Create condition/ConditionPOST
Search medications/MedicationRequest?patient=Patient/{id}GET
Create medication/MedicationRequestPOST
Search appointments/Appointment?patient=Patient/{id}GET
Create appointment/AppointmentPOST
Update appointment/Appointment/{id}PUT
Search practitioners/Practitioner?name:contains={name}GET
Find children/RelatedPerson?patient=Patient/{id}&relationship=CHILDGET
Find parent/RelatedPerson?patient=Patient/{id}&relationship=PRNGET
Batch/Transaction/POST