TL;DR
EHR APIs provide access to patient records in platforms such as Epic, Cerner, and Athenahealth. Most modern EHRs expose FHIR (Fast Healthcare Interoperability Resources) APIs and use OAuth 2.0 with SMART on FHIR extensions for authentication and patient context. Before connecting to a production EHR, validate FHIR resources, authentication flows, search behavior, and error handling against sandbox environments.
Introduction
Electronic Health Records (EHRs) store patient-care data, including diagnoses, medications, lab results, allergies, encounters, and treatment plans. Healthcare organizations manage this information in EHR platforms such as Epic, Cerner, and Athenahealth.
When building a healthcare application, you typically integrate with EHR APIs instead of importing CSV exports. These APIs handle protected health information (PHI), so your integration must account for security, access controls, and regulations such as HIPAA in the United States.
Most EHR vendors now support FHIR, a standard API format for healthcare data exchange. The API format is standardized, but authentication, authorization, data mapping, and vendor-specific behavior still require implementation work.
Use Apidog to test FHIR resources, validate response structures, and run requests against public sandboxes before connecting to a hospital production system.
Test FHIR APIs with Apidog - free
By the end of this guide, you will be able to:
- Identify common FHIR resource types
- Authenticate with SMART on FHIR
- Query patient demographics and clinical data
- Create and update FHIR resources
- Test against EHR sandbox environments
Understand FHIR
FHIR stands for Fast Healthcare Interoperability Resources. It is a healthcare API standard that defines:
-
Resources: Data models for healthcare concepts such as
Patient,Observation, andMedicationRequest - REST operations: Standard HTTP methods for reading, searching, creating, and updating resources
- Representations: JSON and XML formats
Base URL structure
A typical FHIR endpoint uses this pattern:
https://ehr.example.com/fhir/r4/{resource-type}/{id}
For example, retrieve a patient with ID 123:
GET https://ehr.example.com/fhir/r4/Patient/123
Choose the FHIR version
Most current EHR integrations use FHIR R4. Some older integrations still use DSTU2.
Before building your client:
- Confirm the vendor's supported FHIR version.
- Use the matching resource definitions and search parameters.
- Test against the vendor's sandbox for that version.
This guide uses FHIR R4 examples.
Common FHIR resource types
| Resource | Purpose |
|---|---|
Patient |
Demographics and administrative data |
Practitioner |
Healthcare providers |
Organization |
Hospitals and clinics |
Observation |
Lab results and vital signs |
MedicationRequest |
Prescriptions |
Condition |
Diagnoses and problems |
Encounter |
Visits and admissions |
DocumentReference |
Clinical documents |
AllergyIntolerance |
Allergies and adverse reactions |
Work with FHIR resource structures
FHIR resources are JSON documents with a required resourceType field. Read the resource definition before sending writes: different resources have different required fields, terminology requirements, and validation rules.
Patient resource example
{
"resourceType": "Patient",
"id": "123",
"active": true,
"name": [
{
"use": "official",
"family": "Smith",
"given": ["John", "Michael"]
}
],
"gender": "male",
"birthDate": "1985-03-15",
"address": [
{
"use": "home",
"line": ["123 Main St"],
"city": "Boston",
"state": "MA",
"postalCode": "02101",
"country": "USA"
}
],
"telecom": [
{
"system": "phone",
"value": "555-123-4567",
"use": "home"
},
{
"system": "email",
"value": "john.smith@example.com"
}
],
"identifier": [
{
"system": "http://hospital.example.org/mrn",
"value": "MRN-123456"
}
]
}
Observation resource example
Use Observation for measurements, lab results, and vital signs. This example represents a systolic blood-pressure reading.
{
"resourceType": "Observation",
"id": "obs-123",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}
]
},
"subject": {
"reference": "Patient/123"
},
"effectiveDateTime": "2026-03-24T09:30:00Z",
"valueQuantity": {
"value": 120,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
}
}
Authenticate with SMART on FHIR
SMART on FHIR extends OAuth 2.0 for healthcare applications. It adds conventions for EHR discovery, scopes, and patient context.
A typical user-facing application follows this sequence:
- Discover the EHR authorization and token endpoints.
- Redirect the user to the EHR authorization page.
- Exchange the returned authorization code for an access token.
- Use the token for FHIR requests.
- Use patient context from the token response when available.
Step 1: Discover SMART configuration
Request the SMART configuration document:
GET https://ehr.example.com/fhir/r4/.well-known/smart-configuration
Example response:
{
"authorization_endpoint": "https://ehr.example.com/oauth2/authorize",
"token_endpoint": "https://ehr.example.com/oauth2/token",
"scopes_supported": [
"patient/*.read",
"patient/*.write",
"user/*.read",
"launch/patient"
]
}
Store these endpoints in environment variables instead of hardcoding them across requests.
Step 2: Redirect the user for authorization
Build an authorization URL with your registered client ID, redirect URI, requested scopes, and a random state value.
https://ehr.example.com/oauth2/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=patient/*.read&
state=random_state_value
Use a unique state value per authorization request and validate it when the user returns to your callback.
Step 3: Exchange the authorization code for a token
After the authorization server redirects to your callback, exchange the returned code for an access token.
curl -X POST "https://ehr.example.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://yourapp.com/callback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Example token response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/*.read",
"patient": "123"
}
The patient field supplies the patient ID context when the authorization flow grants patient-level access.
Request the minimum SMART scopes
Request only the scope required for the operation you are implementing.
| Scope | Access |
|---|---|
patient/*.read |
Read all patient data |
patient/Patient.read |
Read patient demographics only |
patient/Observation.read |
Read observations only |
user/*.read |
Read all data for the authorized user |
launch/patient |
EHR launches your app with patient context |
For example, prefer this scope when an application only reads vital signs:
patient/Observation.read
Avoid requesting patient/*.read unless your application genuinely needs access to multiple patient resource types.
Query patient data
Set the access token on every protected request:
Authorization: Bearer ACCESS_TOKEN
Get patient demographics
curl -X GET "https://ehr.example.com/fhir/r4/Patient/123" \
-H "Authorization: Bearer ACCESS_TOKEN"
Search for observations
Use FHIR search parameters rather than downloading all resources.
curl -X GET "https://ehr.example.com/fhir/r4/Observation?patient=123&category=vital-signs" \
-H "Authorization: Bearer ACCESS_TOKEN"
FHIR search results are returned as a Bundle:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 5,
"entry": [
{
"resource": {
"...": "Observation resource"
}
}
]
}
In your client code, read resources from entry[].resource and handle the case where entry is absent or empty.
Use common search parameters
# Lab results by date range
GET /Observation?patient=123&category=laboratory&date=gt2026-01-01
# Specific LOINC code
GET /Observation?patient=123&code=http://loinc.org|8480-6
# Active medications
GET /MedicationRequest?patient=123&status=active
# Active conditions
GET /Condition?patient=123&clinical-status=active
# Ambulatory encounters
GET /Encounter?patient=123&type=AMB
Handle pagination
FHIR servers paginate large result sets. Request a page size with _count, then follow the URL where relation is next.
GET /Observation?patient=123&_count=20
Example pagination link:
{
"link": [
{
"relation": "next",
"url": "https://ehr.example.com/fhir/r4/Observation?patient=123&_count=20&page=2"
}
]
}
Do not construct the next page URL manually. Use the server-provided next URL.
Create and update resources
Before enabling writes in production:
- Confirm that the EHR supports writes for the target resource.
- Request a scope that allows the operation.
- Validate required fields and terminology codes.
- Test the same request in a sandbox first.
Create an observation
Send FHIR JSON with Content-Type: application/fhir+json.
curl -X POST "https://ehr.example.com/fhir/r4/Observation" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}]
},
"subject": {
"reference": "Patient/123"
},
"effectiveDateTime": "2026-03-24T09:30:00Z",
"valueQuantity": {
"value": 118,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
}
}'
Update a patient
A PUT request updates the resource at a known ID.
curl -X PUT "https://ehr.example.com/fhir/r4/Patient/123" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Patient",
"id": "123",
"name": [{
"family": "Smith",
"given": ["John", "Michael"]
}],
"telecom": [{
"system": "phone",
"value": "555-999-8888",
"use": "home"
}]
}'
Check vendor-specific endpoints
FHIR provides a common model, but each vendor can have different registration processes, extensions, available resources, and sandbox behavior.
Epic
Epic's FHIR API is available at many large hospitals.
- Base URL: https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/
- Sandbox: https://fhir.epic.com/test/api/FHIR/R4/
- Documentation: https://fhir.epic.com
Epic requires app registration in its app marketplace for production access.
Cerner
Cerner (Oracle Health) uses standard FHIR with some extensions.
- Base URL: https://fhir-myrecord.cerner.com/r4/{tenant-id}
- Sandbox: https://fhir-deprecated.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d
- Documentation: https://docs.oracle.com/en/health/health-cerner/
Athenahealth
Athenahealth provides FHIR and legacy APIs.
- Base URL: https://api.platform.athenahealth.com/fhir/r4/{practice-id}
- Sandbox: Available through the developer program
- Documentation: https://docs.athenahealth.com
Test FHIR APIs with Apidog
Healthcare APIs require careful testing because patient data is sensitive. Use mock or sandbox patient data during development.
1. Start with public sandboxes
Use public FHIR sandboxes to verify request construction, resource validation, searches, and response parsing.
# HAPI FHIR (open source)
https://hapi.fhir.org/baseR4
# SMART Health IT sandbox
https://launch.smarthealthit.org
2. Add resource validation tests
Validate that returned resources contain the fields your application depends on.
pm.test("Resource is valid Patient", () => {
const response = pm.response.json();
pm.expect(response.resourceType).to.eql("Patient");
pm.expect(response.id).to.exist;
pm.expect(response.name).to.be.an("array");
});
pm.test("Observation has required fields", () => {
const resource = pm.response.json();
pm.expect(resource.status).to.exist;
pm.expect(resource.code).to.exist;
pm.expect(resource.subject).to.exist;
});
3. Configure the SMART on FHIR flow
Store configuration in an environment rather than embedding secrets in requests.
AUTHORIZATION_ENDPOINT: https://ehr.example.com/oauth2/authorize
TOKEN_ENDPOINT: https://ehr.example.com/oauth2/token
CLIENT_ID: your_client_id
CLIENT_SECRET: your_client_secret
SCOPE: patient/*.read
Test FHIR APIs with Apidog - free
Apply compliance requirements
HIPAA
In the United States, applications handling PHI must comply with HIPAA. Implementation concerns include:
- Data transmission: Use TLS 1.2 or later.
- Data storage: Encrypt stored data at rest.
- Access control: Maintain audit logging and enforce minimum-necessary access.
- Business Associate Agreements: Required with EHR vendors when applicable.
SMART on FHIR security practices
Account for the lifecycle of OAuth tokens:
- Access tokens expire, typically after one hour.
- Refresh tokens may be available for extended sessions.
- Patient context is bound to the token scope.
- Logout requires token revocation.
Minimize data access
Design each API request and scope around the minimum data necessary for the feature.
- Good:
patient/Observation.read - Avoid unless necessary:
patient/*.read
Troubleshoot common errors
401 Unauthorized
Cause: The token is expired or invalid.
Fix: Refresh the token using the refresh token received during initial authorization.
403 Forbidden
Cause: The current scope does not permit access to the requested resource.
Fix: Check the scopes granted to the token. Request additional scopes during authorization only if the application requires them.
404 Not Found
Cause: The patient or resource does not exist, or it is not available in the current patient context.
Fix: Verify the resource ID and confirm that the current token can access the patient.
422 Unprocessable Entity
Cause: FHIR resource validation failed.
Fix: Inspect the OperationOutcome response, then correct required fields or terminology codes.
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "required",
"details": {
"text": "Observation.status is required"
}
}
]
}
Compare EHR API options
| Feature | Epic | Cerner | Athenahealth | OpenEMR |
|---|---|---|---|---|
| FHIR R4 | ✓ | ✓ | ✓ | Partial |
| SMART on FHIR | ✓ | ✓ | ✓ | No |
| Sandbox access | ✓ | ✓ | Limited | Self-host |
| API documentation | Excellent | Good | Good | Basic |
| Market share | Large hospitals | Health systems | Small practices | Open source |
Epic and Cerner are common in large healthcare systems. Athenahealth primarily serves smaller practices. OpenEMR is open source but has limited API support.
Apply FHIR APIs to real-world use cases
Patient portal
A health system builds a portal that pulls data from Epic. Patients can view lab results, medications, and upcoming appointments. The portal uses FHIR APIs with SMART on FHIR authentication.
Clinical research
A pharmaceutical company identifies patients eligible for clinical trials by querying FHIR APIs across hospital systems, with proper consent management.
Remote monitoring
A telehealth company sends patient-reported vital signs to an EHR. It creates Observation resources through the FHIR API so clinicians can view the data in Epic.
Next steps
You now have the core workflow for building an EHR integration:
- Use FHIR resources to model healthcare data.
- Use SMART on FHIR for OAuth 2.0 authentication and patient context.
- Query resources with FHIR search parameters.
- Follow server-provided pagination links.
- Validate writes and error responses in a sandbox.
- Apply HIPAA-compliant handling for PHI.
Start by exploring the HAPI FHIR public sandbox, identify the resource types required by your application, register for the relevant EHR developer programs, and test your requests with mock patient data before production.
Test FHIR APIs with Apidog - free
FAQ
What is the difference between FHIR and HL7 v2?
HL7 v2 is an older messaging standard commonly used in hospital interfaces. FHIR is a modern REST API standard. Most new integrations use FHIR, though HL7 v2 remains common in legacy systems.
Do I need a BAA to use EHR APIs?
Yes, if you handle PHI. Business Associate Agreements are required between covered entities and business associates. Confirm requirements with the EHR vendor's compliance team.
How do I get access to Epic's FHIR API?
Register through Epic's App Orchard marketplace. Use the public sandbox for testing. Production access requires hospital approval.
What is patient context?
SMART on FHIR tokens can include a patient ID. API calls are limited to that patient's data, ensuring that applications access only data authorized by the patient.
Can I write data to EHRs?
Yes, with limitations. Many EHRs allow creating observations and updating patient demographics. Writing diagnoses or medications commonly requires clinical decision support approval.
How do I handle terminology codes?
FHIR uses standard terminology systems:
- LOINC for lab tests and observations
- SNOMED CT for clinical concepts
- ICD-10 for diagnoses
- RxNorm for medications
Use the appropriate terminology system when creating resources.
What about international healthcare?
FHIR is a global standard. Countries and regions can publish their own implementation guides. The United States uses US Core profiles. Check the specifications for your region.

Top comments (0)