TL;DR
EHR APIs allow you to access patient health records stored in systems like Epic, Cerner, and Athenahealth. Most modern EHRs support FHIR (Fast Healthcare Interoperability Resources), a standard format for healthcare data. Authentication uses OAuth 2.0 with SMART on FHIR extensions. For testing, use Apidog to validate FHIR resources, test against sandboxes, and ensure your integration handles patient data correctly before connecting to production systems.
Introduction
Electronic Health Records (EHRs) store all aspects of patient care: diagnoses, medications, lab results, allergies, and treatment plans. Hospitals, clinics, and health systems use EHR platforms like Epic, Cerner, and Athenahealth.
To build healthcare applications, you need to connect directly to these systems via APIs. EHR APIs are not like typical REST APIs—they handle protected health information (PHI) and must adhere to regulations like HIPAA in the US.
Most EHR vendors now support FHIR, a standard API format. However, authentication, authorization, and data mapping can still be challenging.
If you're building healthcare integrations, Apidog helps you test FHIR resources, validate data structures, and ensure your app handles patient data correctly. Test against public sandboxes before connecting to real hospital systems.
By the end of this guide, you’ll know how to:
- Understand FHIR resource types and structure
- Authenticate with SMART on FHIR
- Query patient demographics and clinical data
- Create and update patient resources
- Test with EHR sandboxes
Understanding FHIR
FHIR (Fast Healthcare Interoperability Resources) is the current standard for healthcare APIs. It defines:
- Resources: Data types for healthcare concepts (e.g., Patient, Observation, Medication)
- REST API: Standard CRUD operations on resources
- Formats: JSON and XML representations
Base URL Structure
https://ehr.example.com/fhir/r4/{resource-type}/{id}
Example:
GET https://ehr.example.com/fhir/r4/Patient/123
FHIR Version
Most EHRs use FHIR R4 (Release 4). Some older systems may use DSTU2, but this guide focuses on R4.
Resource Types
| Resource | Purpose |
|---|---|
| Patient | Demographics, admin data |
| Practitioner | Healthcare providers |
| Organization | Hospitals, clinics |
| Observation | Lab results, vital signs |
| MedicationRequest | Prescriptions |
| Condition | Diagnoses, problems |
| Encounter | Visits, admissions |
| DocumentReference | Clinical documents |
| AllergyIntolerance | Allergies, adverse reactions |
FHIR Resource Structure
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
{
"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]"
}
}
Authentication with SMART on FHIR
SMART on FHIR extends OAuth 2.0 for healthcare, handling patient context and EHR-specific scopes.
OAuth Flow for Patient Access
Step 1: Get Authorization URL
GET https://ehr.example.com/fhir/r4/.well-known/smart-configuration
Sample 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"
]
}
Step 2: Redirect User to Authorize
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
Step 3: Exchange Code for 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"
Token Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/*.read",
"patient": "123"
}
The patient field gives you the patient ID context.
SMART Scopes
| Scope | Access Description |
|---|---|
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 authorized user |
launch/patient |
EHR launches your app with patient context |
Querying Patient Data
Get Patient Demographics
curl -X GET "https://ehr.example.com/fhir/r4/Patient/123" \
-H "Authorization: Bearer ACCESS_TOKEN"
Search for Observations
curl -X GET "https://ehr.example.com/fhir/r4/Observation?patient=123&category=vital-signs" \
-H "Authorization: Bearer ACCESS_TOKEN"
Sample Bundle Response:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 5,
"entry": [
{
"resource": { ... Observation resource ... }
}
]
}
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
# Medications
GET /MedicationRequest?patient=123&status=active
# Conditions (diagnoses)
GET /Condition?patient=123&clinical-status=active
# Encounters
GET /Encounter?patient=123&type=AMB
Pagination
For large result sets, use pagination:
GET /Observation?patient=123&_count=20
Paginated Response:
{
"link": [
{
"relation": "next",
"url": "https://ehr.example.com/fhir/r4/Observation?patient=123&_count=20&page=2"
}
]
}
Creating and Updating Resources
Create an Observation
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
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"
}]
}'
EHR Vendor Specifics
Epic
-
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
App registration in their marketplace is required for production access.
Cerner
-
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
-
Base URL:
https://api.platform.athenahealth.com/fhir/r4/{practice-id} - Sandbox: Available through developer program
- Documentation: https://docs.athenahealth.com
Testing with Apidog
Healthcare APIs require thorough testing due to the sensitivity of patient data.
1. Use Public Sandboxes
Test integrations against public FHIR sandboxes before production:
# HAPI FHIR (open source)
https://hapi.fhir.org/baseR4
# SMART Health IT sandbox
https://launch.smarthealthit.org
2. Validate FHIR Resources
Use automated scripts to ensure required fields are present:
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. Test Authentication Flow
Save your SMART configuration in your environment:
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
Compliance Considerations
HIPAA
In the US, applications handling healthcare data must comply with HIPAA:
- Data transmission: Use TLS 1.2+
- Data storage: Encrypt at rest
- Access control: Implement audit logging and least-privilege access
- Business Associate Agreement: Required with EHR vendors
SMART on FHIR Security
- Tokens expire (typically 1 hour)
- Refresh tokens are available for longer sessions
- Patient context is bound to the token scope
- Logout requires token revocation
Data Minimization
Only request the scopes you need:
- Prefer:
patient/Observation.read - Avoid:
patient/*.readunless necessary
Common Errors and Fixes
401 Unauthorized
Cause: Token expired or invalid.
Fix: Refresh your access token using the refresh token from initial authorization.
403 Forbidden
Cause: Scope does not cover the requested resource.
Fix: Ensure your requested scopes are sufficient. Request additional scopes if needed.
404 Not Found
Cause: Patient or resource does not exist.
Fix: Verify resource IDs and confirm the patient is accessible with your current token context.
422 Unprocessable Entity
Cause: FHIR resource validation failed.
Fix: Check required fields and code systems. Example error:
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "required",
"details": {
"text": "Observation.status is required"
}
}]
}
Alternatives and Comparisons
| 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 hosp. | Health systems | Small pract. | Open src. |
Epic and Cerner are most common in large healthcare systems. Athenahealth is used by smaller practices. OpenEMR is open source but offers limited API support.
Real-World Use Cases
- Patient portal: Health systems pull data from Epic to display lab results, meds, and appointments to patients via FHIR APIs and SMART on FHIR authentication.
- Clinical research: Pharma companies identify eligible trial patients by querying FHIR APIs across multiple systems, with consent management.
- Remote monitoring: Telehealth companies integrate patient-reported vitals with EHRs by creating Observations via FHIR API, making them available to clinicians in Epic.
Wrapping Up
Key takeaways:
- FHIR is the standard for healthcare APIs
- Resources represent concepts like patients and observations
- SMART on FHIR handles OAuth with patient context
- Each EHR vendor has specific implementation details
- Always test with sandboxes before moving to production
Next steps:
- Explore the HAPI FHIR public sandbox
- Understand necessary FHIR resource types for your app
- Register for EHR developer programs
- Test with Apidog using mock patient data
- Implement HIPAA-compliant data handling
FAQ
What’s the difference between FHIR and HL7 v2?
HL7 v2 is an older messaging standard for hospital interfaces. FHIR is a modern REST API standard. Most new integrations use FHIR, but HL7 v2 is still common in legacy systems.
Do I need a BAA to use EHR APIs?
Yes, if handling PHI. A Business Associate Agreement is 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 in Epic’s App Orchard marketplace. For sandbox testing, use their public sandbox. Production access requires hospital approval.
What’s a patient context?
SMART on FHIR tokens include a patient ID. API calls are limited to that patient’s data, restricting access to only what’s authorized.
Can I write data to EHRs?
Yes, with limitations. Most EHRs allow creating Observations and updating patient demographics. Writing diagnoses or medications often requires additional clinical approval.
How do I handle terminology codes?
FHIR uses standard terminologies:
- LOINC for labs/observations
- SNOMED CT for clinical concepts
- ICD-10 for diagnoses
- RxNorm for medications Use these when creating resources.
What about international healthcare?
FHIR is global, but each country may have its own implementation guide. The US uses US Core profiles. Check your region’s specifications.

Top comments (0)