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.
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
- Overview
- Authentication
- Patient Endpoints
- Observation Endpoints
- Condition Endpoints
- MedicationRequest Endpoints
- Appointment Endpoints
- Practitioner Endpoints
- RelatedPerson Endpoints
- Batch Operations
- 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:
| Parameter | Description | Example |
|---|---|---|
identifier | National ID or MRN | 9001015009087 |
name | Partial name match | Smith |
telecom | Phone number | +27821234567 |
_count | Max 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 Createdif new patient created - Returns
200 OKif 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:
| Parameter | Description | Example |
|---|---|---|
patient | Patient reference (required) | Patient/patient-123 |
code | LOINC/SNOMED codes (comma-separated) | 8310-5,9279-1 |
category | Observation category | vital-signs |
_sort | Sort order | -_lastUpdated |
_count | Max results | 50 |
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:
| Parameter | Description | Example |
|---|---|---|
patient | Patient reference (required) | Patient/patient-123 |
clinical-status | Filter by status | active |
_sort | Sort 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:
| Parameter | Description | Example |
|---|---|---|
patient | Patient reference (required) | Patient/patient-123 |
status | Prescription status | active |
_sort | Sort 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:
| Parameter | Description | Example |
|---|---|---|
patient | Patient reference | Patient/patient-123 |
actor | Any participant (patient, practitioner, location) | Practitioner/prac-456 |
status | Appointment status | booked |
date | Date filter (supports ge, le, gt, lt) | ge2024-01-01 |
_sort | Sort 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:
| Parameter | Description | Example |
|---|---|---|
name:contains | Partial name match | Smith |
specialty:text | Specialty text search | Cardiology |
_count | Max results | 10 |
_sort | Sort order | name |
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:
- Get Patient ID from contact:
contact.fhir_patient_id - 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 Status | Meaning |
|---|---|
| 200 | Success (read/search/conditional create matched) |
| 201 | Created |
| 400 | Bad request (invalid resource) |
| 401 | Unauthorized |
| 404 | Resource not found |
| 422 | Unprocessable entity (validation failed) |
Quick Reference
| Operation | Endpoint | Method |
|---|---|---|
| Search patients | /Patient?{params} | GET |
| Read patient | /Patient/{id} | GET |
| Create patient | /Patient | POST |
| Update patient | /Patient/{id} | PUT |
| Search observations | /Observation?patient=Patient/{id} | GET |
| Create observation | /Observation | POST |
| Search conditions | /Condition?patient=Patient/{id} | GET |
| Create condition | /Condition | POST |
| Search medications | /MedicationRequest?patient=Patient/{id} | GET |
| Create medication | /MedicationRequest | POST |
| Search appointments | /Appointment?patient=Patient/{id} | GET |
| Create appointment | /Appointment | POST |
| Update appointment | /Appointment/{id} | PUT |
| Search practitioners | /Practitioner?name:contains={name} | GET |
| Find children | /RelatedPerson?patient=Patient/{id}&relationship=CHILD | GET |
| Find parent | /RelatedPerson?patient=Patient/{id}&relationship=PRN | GET |
| Batch/Transaction | / | POST |