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