Documentation/api/FHIR_API.md
Complete guide to OpenEMR's FHIR R4 API implementation.
OpenEMR provides a comprehensive FHIR R4 implementation compliant with:
| Standard | Version | Status |
|---|---|---|
| FHIR | R4 (4.0.1) | ✅ Baseline Support |
| US Core | 8.0 | ✅ Compliant |
| SMART on FHIR | v2.2.0 | ✅ Certified |
| Bulk Data | v2.0.0 | ✅ Implemented |
| USCDI | v5 | ✅ Supported |
Navigate to: Administration → Config → Connectors
Enable: ☑ Enable OpenEMR Standard FHIR REST API
Required: All FHIR endpoints require HTTPS/TLS.
Set base URL: Administration → Config → Connectors → Site Address (required for OAuth2 and FHIR)
Example: https://your-openemr.example.com or https://localhost:9300 for local testing. If installed in a subdirectory, include it (e.g. https://your-openemr.example.com/openemr).
Note that several curl examples are given in this guide. If your OpenEMR instance is using a self-signed certificate you will need to pass -k to curl to disable certificate verification for testing purposes.
See Authentication Guide for client registration.
Required scopes: At minimum openid + api:fhir + resource-specific scopes.
Example:
openid api:fhir patient/Patient.rs patient/Observation.rs
See Authorization Guide for complete scope listing.
FHIR endpoints use the following base URL pattern:
https://{your-openemr-host}/apis/{site}/fhir
https://localhost:9300/apis/default/fhir
https://localhost:9300/apis/alternate/fhir
{base}/[resource-type]/[id]
{base}/[resource-type]?[search-parameters]
{base}/[resource-type]/[operation]
Examples:
GET https://localhost:9300/apis/default/fhir/Patient/123
GET https://localhost:9300/apis/default/fhir/Observation?patient=123
POST https://localhost:9300/apis/default/fhir/Patient/$docref
All FHIR API requests (except capability statement) require authentication via Bearer token.
curl -X GET 'https://localhost:9300/apis/default/fhir/Patient' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Accept: application/fhir+json'
See Authentication Guide for complete OAuth2 flows:
GET /apis/default/fhir/Patient/123 HTTP/1.1
Host: localhost:9300
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...
Accept: application/fhir+json
Supported content types:
| Format | Content-Type | Accept Header |
|---|---|---|
| JSON (default) | application/fhir+json | application/fhir+json |
| JSON | application/json | application/json |
| XML | application/fhir+xml | application/fhir+xml |
Recommended: Use application/fhir+json for FHIR-specific JSON.
The Capability Statement describes the FHIR server's capabilities.
GET /fhir/metadata
No authentication required for capability statement.
curl -X GET 'https://localhost:9300/apis/default/fhir/metadata' \
-H 'Accept: application/fhir+json'
{
"resourceType": "CapabilityStatement",
"status": "active",
"date": "2025-11-25",
"kind": "instance",
"instantiates": [
"http://hl7.org/fhir/us/core/CapabilityStatement/us-core-server",
"http://hl7.org/fhir/uv/bulkdata/CapabilityStatement/bulk-data"
],
"software": {
"name": "OpenEMR",
"version": "7.0.4"
},
"implementation": {
"description": "OpenEMR FHIR API",
"url": "https://localhost:9300/apis/default/fhir"
},
"fhirVersion": "4.0.1",
"format": [
"application/json"
],
"rest": [
{
"mode": "server",
"security": {
"extension": [
{
"valueCode": "launch-ehr",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "context-passthrough-banner",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "context-ehr-patient",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "context-passthrough-style",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "sso-openid-connect",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "client-confidential-symmetric",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "permission-user",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "context-standalone-patient",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "launch-standalone",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "permission-patient",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "permission-offline",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
},
{
"valueCode": "client-public",
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
}
],
"service": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/restful-security-service",
"code": "SMART-on-FHIR",
"display": "SMART-on-FHIR"
}
],
"text": "OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org)"
}
]
},
"resource": [
{
"type": "Patient",
"profile": "http://hl7.org/fhir/StructureDefinition/Patient",
"supportedProfile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient",
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|3.1.1",
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|8.0.0"
],
"interaction": [
{
"code": "create"
},
{
"code": "update"
},
{
"code": "search-type"
},
{
"code": "read"
}
],
"updateCreate": false,
"searchInclude": [
"*"
],
"searchRevInclude": [
"Provenance:target"
],
"searchParam": [
{
"name": "_id",
"type": "token"
},
{
"name": "identifier",
"type": "token"
},
{
"name": "name",
"type": "string"
},
{
"name": "birthdate",
"type": "date"
},
{
"name": "gender",
"type": "token"
},
{
"name": "address",
"type": "string"
},
{
"name": "address-city",
"type": "string"
},
{
"name": "address-postalcode",
"type": "string"
},
{
"name": "address-state",
"type": "string"
},
{
"name": "email",
"type": "token"
},
{
"name": "family",
"type": "string"
},
{
"name": "given",
"type": "string"
},
{
"name": "phone",
"type": "token"
},
{
"name": "telecom",
"type": "token"
},
{
"name": "_lastUpdated",
"type": "date"
},
{
"name": "generalPractitioner",
"type": "reference"
}
],
"operation": [
{
"name": "export",
"definition": "http://hl7.org/fhir/uv/bulkdata/OperationDefinition/patient-export"
}
]
}
]
}
]
}
The capability statement reveals:
OpenEMR supports 30 FHIR R4 resources across all contexts (patient, user, system).
The resources that OpenEMR supports is documented via Swagger. You can see this documentation (and can test it) by going to the swagger directory in your OpenEMR installation. The FHIR API is documented there in the fhir section. Can also see (and test) this in the online demos at https://www.open-emr.org/wiki/index.php/Development_Demo#Daily_Build_Development_Demos (clicking on the API (Swagger) User Interface link for the demo will take you there).
OpenEMR implements the FHIR Bulk Data Export specification for large-scale data access.
Bulk exports enable:
Authentication: Client Credentials Grant with JWKS required
Scopes: System-level export scopes
Format: NDJSON (Newline Delimited JSON)
| Export Type | Scope | Endpoint | Data Scope |
|---|---|---|---|
| System Export | system/*.$export | GET /fhir/$export | All data |
| Patient Export | system/Patient.$export | GET /fhir/Patient/$export | All patient compartment data |
| Group Export | system/Group.$export | GET /fhir/Group/[id]/$export | Group patient compartment data |
Export all supported resources for all patients.
system/*.$export
system/*.$bulkdata-status
system/Binary.read
Replace TOKEN with your access token.
curl -X GET 'https://localhost:9300/apis/default/fhir/$export' \
-H 'Authorization: Bearer TOKEN' \
-H 'Accept: application/fhir+json' \
-H 'Prefer: respond-async'
HTTP/1.1 202 Accepted
Content-Location: https://localhost:9300/apis/default/fhir/$bulkdata-status?job=92a94c00-77d6-4dfc-ae3b
The Content-Location header contains the status polling URL.
Export all patient compartment data for all patients.
system/Patient.$export
system/*.$bulkdata-status
system/Binary.read
Replace TOKEN with your access token.
curl -X GET 'https://localhost:9300/apis/default/fhir/Patient/$export' \
-H 'Authorization: Bearer TOKEN' \
-H 'Accept: application/fhir+json' \
-H 'Prefer: respond-async'
Includes all resources in the Patient Compartment:
Export data for a specific group of patients. In OpenEMR a Group is automatically created for every practitioner containing their assigned patients. Patients are included based on their primary care provider
system/Group.$export
system/*.$bulkdata-status
system/Binary.read
curl -X GET 'https://localhost:9300/apis/default/fhir/Group/1/$export' \
-H 'Authorization: Bearer TOKEN' \
-H 'Accept: application/fhir+json' \
-H 'Prefer: respond-async'
OpenEMR automatically creates groups:
patient_data.providerID field in the OpenEMR system.uuid column from the uuid_mapping table where the resource_type is Group and the target_uuid is the practitioner's ID converted to binary.Example: Group 5 contains all patients with Practitioner 5 as PCP.
Check export job status using the Content-Location URL.
system/*.$bulkdata-status
curl -X GET 'https://localhost:9300/apis/default/fhir/$bulkdata-status?job=92a94c00-77d6-4dfc-ae3b' \
-H 'Authorization: Bearer TOKEN'
HTTP/1.1 202 Accepted
{
"transactionTime": "2024-01-15T14:30:00.000Z",
"request": "/apis/default/fhir/Group/1/$export",
"requiresAccessToken": true,
"output": [
{
"type": "Patient",
"url": "https://localhost:9300/apis/default/fhir/Binary/97552"
},
{
"type": "Observation",
"url": "https://localhost:9300/apis/default/fhir/Binary/97553"
},
{
"type": "Condition",
"url": "https://localhost:9300/apis/default/fhir/Binary/97554"
},
{
"type": "MedicationRequest",
"url": "https://localhost:9300/apis/default/fhir/Binary/97555"
}
],
"error": []
}
Fields:
transactionTime - When export completedrequest - Original export requestrequiresAccessToken - If true, use Bearer token to downloadoutput - Array of output files by resource typeerror - Array of error files (if any)Download exported NDJSON files using Binary resource URLs.
system/Binary.read
curl -X GET 'https://localhost:9300/apis/default/fhir/Binary/97552' \
-H 'Authorization: Bearer TOKEN' \
-o patients.ndjson
{"resourceType":"Patient","id":"1",...}
{"resourceType":"Patient","id":"2",...}
{"resourceType":"Patient","id":"3",...}
Each line is a complete JSON FHIR resource for that resource type.
# Step 1: Initiate export (get new token - client credentials)
TOKEN=$(curl -X POST https://localhost:9300/oauth2/default/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode "client_assertion=$JWT" \
--data-urlencode 'scope=system/Patient.$export system/*.$bulkdata-status system/Binary.read' \
| jq -r '.access_token')
# Step 2: Start export
CONTENT_LOCATION=$(curl -X GET \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/fhir+json" \
-H "Prefer: respond-async" \
-i https://localhost:9300/apis/default/fhir/Patient/\$export \
| grep -i 'Content-Location:' | cut -d' ' -f2 | tr -d '\r')
# Step 3: Poll for completion
while true; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"$CONTENT_LOCATION")
if [ "$STATUS" == "200" ]; then
echo "Export complete!"
break
else
echo "Export in progress... (Status: $STATUS)"
sleep 30
fi
done
# Step 4: Get download URLs
EXPORT_MANIFEST=$(curl -s -H "Authorization: Bearer $TOKEN" "$CONTENT_LOCATION")
echo "$EXPORT_MANIFEST" | jq -r '.output[] | "\(.type): \(.url)"'
# Step 5: Download files
echo "$EXPORT_MANIFEST" | jq -r '.output[].url' | while read URL; do
FILENAME=$(echo "$URL" | sed 's/.*Binary\///' | sed 's/$/.ndjson/')
curl -H "Authorization: Bearer $TOKEN" "$URL" -o "$FILENAME"
echo "Downloaded: $FILENAME"
done
Customize exports with query parameters:
Filter by resource type:
GET /fhir/$export?_type=Patient,Observation,Condition
Filter by date:
GET /fhir/$export?_since=2024-01-01T00:00:00Z
Combine filters:
GET /fhir/Patient/$export?_type=Observation,Condition&_since=2024-01-01T00:00:00Z
Generate Continuity of Care Documents (CCD) on demand.
The $docref operation creates clinical summary documents (C-CDA) for:
patient/DocumentReference.$docref
patient/DocumentReference.read
patient/Binary.read
Or user/system context equivalents.
curl -X POST 'https://localhost:9300/apis/default/fhir/DocumentReference/$docref' \
-H 'Authorization: Bearer TOKEN' \
-H 'Content-Type: application/fhir+json' \
--data '{
"resourceType": "Parameters",
"parameter": [
{
"name": "patient",
"valueId": "123"
},
{
"name": "start",
"valueDate": "2024-01-01"
},
{
"name": "end",
"valueDate": "2024-12-31"
}
]
}'
| Parameter | Type | Required | Description |
|---|---|---|---|
patient | id | Yes | Patient ID |
start | date | No | Start date for encounter filtering |
end | date | No | End date for encounter filtering |
type | code | No | Document type code |
No dates: Entire patient history
Start date only: From start date to present
End date only: All history up to end date
Both dates: Specific date range
Same start/end: Single day
Precision:
YYYY - Full yearYYYY-MM - Full monthYYYY-MM-DD - Specific day{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [{
"resource": {
"resourceType": "DocumentReference",
"id": "ccd-123-20240115",
"status": "current",
"type": {
"coding": [{
"system": "http://loinc.org",
"code": "34133-9",
"display": "Summarization of Episode Note"
}]
},
"category": [{
"coding": [{
"system": "http://hl7.org/fhir/us/core/CodeSystem/us-core-documentreference-category",
"code": "clinical-note"
}]
}],
"subject": {
"reference": "Patient/123"
},
"date": "2024-01-15T14:30:00Z",
"content": [{
"attachment": {
"contentType": "application/xml",
"url": "https://localhost:9300/apis/default/fhir/Binary/98765"
}
}]
}
}]
}
# Get CCD XML
curl -X GET 'https://localhost:9300/apis/default/fhir/Binary/98765' \
-H 'Authorization: Bearer TOKEN' \
-o patient-ccd.xml
Documents include:
Date-Filtered Sections (encounter-based):
Full History Sections:
Option 1: XSL Transform
Download XSL stylesheet:
GET /interface/modules/zend_modules/public/xsl/cda.xsl
Place in same directory as CCD XML for browser rendering.
Option 2: Upload to OpenEMR
Upload XML to patient documents under "CCDA" category for human-readable view.
Complete tutorial with screenshots: https://github.com/openemr/openemr/issues/5284#issuecomment-1155678620
| Code | Meaning | Common Causes |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource created successfully |
| 202 | Accepted | Bulk export initiated |
| 400 | Bad Request | Invalid request format |
| 401 | Unauthorized | Missing or invalid token |
| 403 | Forbidden | Insufficient scopes |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable Entity | Validation error |
| 500 | Internal Server Error | Server error |
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "security",
"diagnostics": "Insufficient scope for requested resource"
}]
}
401 Unauthorized - Missing Token
{
"error": "invalid_token",
"error_description": "The access token is missing"
}
Solution: Include Authorization: Bearer TOKEN header
403 Forbidden - Insufficient Scopes
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "security",
"diagnostics": "Insufficient scope: patient/Observation.rs required"
}]
}
Solution: Request appropriate scopes during authorization
404 Not Found
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "not-found",
"diagnostics": "Resource Patient/99999 not found"
}]
}
Solution: Verify resource ID exists
422 Validation Error
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "invalid",
"diagnostics": "Invalid date format: expected YYYY-MM-DD"
}]
}
Solution: Fix request data format
curl -X GET 'https://localhost:9300/apis/default/fhir/Patient/123' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...' \
-H 'Accept: application/fhir+json'
curl -X GET 'https://localhost:9300/apis/default/fhir/MedicationRequest?patient=123&status=active' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
curl -X GET 'https://localhost:9300/apis/default/fhir/Observation?patient=123&category=vital-signs&date=ge2024-01-01&_count=10' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
curl -X GET 'https://localhost:9300/apis/default/fhir/Observation?patient=123&category=laboratory&_revinclude=Provenance:target' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
curl -X POST 'https://localhost:9300/apis/default/fhir/DocumentReference/$docref' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...' \
-H 'Content-Type: application/fhir+json' \
--data '{
"resourceType": "Parameters",
"parameter": [
{"name": "patient", "valueId": "123"},
{"name": "start", "valueDate": "2024-01-01"},
{"name": "end", "valueDate": "2024-03-31"}
]
}'
# Get all active lab orders
curl -X GET 'https://localhost:9300/apis/default/fhir/ServiceRequest?patient=123&status=active&category=Laboratory' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
# Get dispensing records for last 6 months
curl -X GET 'https://localhost:9300/apis/default/fhir/MedicationDispense?patient=123&whenhandedover=ge2023-07-01' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
# Get related persons for patient
curl -X GET 'https://localhost:9300/apis/default/fhir/RelatedPerson?patient=123' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
# Get specimens collected for service request
curl -X GET 'https://localhost:9300/apis/default/fhir/Specimen?patient=123' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1Qi...'
Swagger UI: Interactive API testing at /swagger/
Online Demos: https://www.open-emr.org/wiki/index.php/Development_Demo
The FHIR API is designed to maintain backwards compatibility for existing integrations. New resources and operations are added in a way that does not break existing functionality.
US Core 3.1, 7.0, and 8.0 has profiles that in some resources conflict with each other. If your specific OpenEMR implementation requires strict adherence to a specific US Core IG version, you can set the Maximum US Core IG version you support in the OpenEMR Admin->Config->Connectors->Maximum supported version for US Core FHIR Implementation Guide to be 3.1, 7.0, or 8.0. This will ensure that the FHIR API only advertises and supports profiles up to that version.
Next Steps:
Support:
Swagger Documentation:
https://your-openemr-install/swagger/This documentation represents the collective knowledge and contributions of the OpenEMR open-source community. The content is based on:
The organization, structure, and presentation of this documentation was enhanced using Claude AI (Anthropic) to:
All technical accuracy is maintained from the original community-authored documentation.
OpenEMR is an open-source project. To contribute to this documentation:
Last Updated: November 2025 License: GPL v3
For complete documentation, see Documentation/api/