Introduction
Integrating with an EHR is never as clean as the vendor documentation suggests. Every implementation team has a story: the [FHIR](/terms/FHIR) endpoint that technically exists but times out for queries over 30 days, the HL7 feed that drops messages when census exceeds 80%, the bulk export that requires a ticket to the vendor's support team before it will actually execute. This guide documents what works in production — not what the integration specs say should work.
The three systems covered here — Epic, Cerner (now Oracle Health), and the Oracle Health platform (HealtheIntent / Millennium) — represent over 70% of US inpatient EHR market share. If you are building a clinical data pipeline, you will encounter at least one of them.
Integration Approaches Overview
Every EHR integration uses one or more of these mechanisms:
| Method | Latency | Coverage | Reliability |
|---|---|---|---|
| FHIR R4 REST API | Near-real-time | Selective resources | Moderate (rate limits, auth complexity) |
| FHIR Bulk Export ($export) | Hours | Broad | High (designed for population export) |
| HL7 v2 ADT/ORU feeds | Near-real-time | ADT + lab results | High (mature, well-understood) |
| Direct database read | Low latency | Full | High risk (unsupported, schema changes break pipelines) |
| Vendor ETL (Chronicles, Clarity, Millennium extract) | Daily/weekly | Broad | Moderate (vendor-dependent) |
The practical architecture for most organizations combines FHIR Bulk Export for historical population data with HL7 v2 real-time feeds for ADT and lab events.
Epic Integration
FHIR R4 API
Epic's FHIR R4 implementation (Interconnect) is SMART on FHIR-compliant and covers the US Core profile. For analytics use cases, the most relevant resources are:
- Patient, Coverage, Encounter, Condition (ICD-10 diagnoses)
- Observation (lab results, vital signs — LOINC-coded)
- MedicationRequest, MedicationAdministration
- Procedure (CPT-coded)
- DocumentReference (clinical notes)
The gotcha: Epic's FHIR API is rate-limited. Typical limits are 500–1000 requests per minute per application. For population-level extraction, bulk export is required — direct FHIR API calls will hit rate limits and result in incomplete data.
Epic Bulk Data Export
Epic supports the FHIR Bulk Data Access specification ($export operation) through its Interconnect API. This is the correct mechanism for extracting full population data.
import requests import time # Initiate bulk export response = requests.post( "https://[EPIC_FHIR_ENDPOINT]/api/FHIR/R4/$export", headers={ "Authorization": f"Bearer {access_token}", "Accept": "application/fhir+json", "Prefer": "respond-async" }, json={ "resourceType": "Parameters", "parameter": [ {"name": "_type", "valueString": "Patient,Encounter,Condition,Observation"}, {"name": "_since", "valueString": "2024-01-01T00:00:00Z"} ] } ) # Epic returns 202 with Content-Location header for polling polling_url = response.headers.get("Content-Location") # Poll until complete (typically 15 min to 2 hours for large populations) while True: status = requests.get(polling_url, headers={"Authorization": f"Bearer {access_token}"}) if status.status_code == 200: export_manifest = status.json() break time.sleep(30)
The gotcha: Epic's bulk export can take 30 minutes to 4 hours depending on population size and server load. Build your pipeline to be asynchronous — do not hold a connection open.
Epic HL7 v2 Feeds
For real-time ADT and lab results, Epic sends HL7 v2 messages via MLLP over TCP/IP. These are mature, reliable, and the correct choice for real-time clinical event ingestion. Typical message types:
ADT^A01— Patient AdmitADT^A03— Patient DischargeADT^A08— Patient Information UpdateORU^R01— Lab Result
Cerner (Oracle Health) Integration
FHIR R4 API
Cerner's FHIR implementation (Cerner Ignite) is also US Core-compliant. Coverage is similar to Epic, with strong support for:
- Patient, Encounter, Condition, Observation, Procedure
- CarePlan, Goal, AllergyIntolerance
- DocumentReference (notes in DSTU2 and R4)
The gotcha: Cerner's FHIR API requires per-patient queries — there is no native population-level query without bulk export. For large health system integrations, this means your extraction logic must be patient-ID-driven.
Cerner Bulk Export
Cerner supports FHIR Bulk Export through its Health Data Intelligence platform. For organizations not on HDI, bulk extraction typically goes through Cerner's Millennium Extract (a direct HL7 or flat-file export from the Millennium database) or through the HealtheDataLab cloud offering.
Practical recommendation for Cerner: If your organization has an existing Millennium reporting database (Cerner's "Lights On" reporting environment or a custom Clarity-equivalent), use direct SQL extraction from the reporting replica rather than FHIR API for large-volume historical loads. FHIR API is better for incremental/delta loads.
Cerner CCL Scripts
Cerner Command Language (CCL) scripts can extract data directly from the Millennium database. This approach is faster than FHIR for bulk extraction but requires a Cerner-credentialed developer and carries schema-change risk:
-- CCL query to extract encounters for a date range SELECT e.encntr_id, e.person_id, e.encntr_type_cd, cv.display AS encounter_type, e.reg_dt_tm, e.disch_dt_tm, p.name_full_formatted AS patient_name, p.birth_dt_tm AS date_of_birth FROM encounter e JOIN person p ON e.person_id = p.person_id JOIN code_value cv ON e.encntr_type_cd = cv.code_value WHERE e.reg_dt_tm >= CNVTDATETIME("01-JAN-2024 00:00:00") AND e.active_ind = 1
Oracle Health (HealtheIntent / Population Health Platform)
Oracle Health (formerly the Cerner population health platform, now integrated with Oracle's broader health platform) takes a different integration approach. Rather than direct EHR extraction, HealtheIntent ingests data from multiple sources (Cerner Millennium, third-party EHRs, claims) into a centralized data repository.
For organizations on HealtheIntent, the integration pattern is:
- Configure HealtheIntent to receive your source data feeds
- Use HealtheIntent's REST API or flat-file exports to extract processed clinical data
- Land processed data in your data warehouse
The advantage: HealtheIntent normalizes data from multiple source systems. If your health system operates multiple EHRs (Epic in one hospital, Cerner in another), HealtheIntent handles the normalization before your warehouse sees it.
The gotcha: HealtheIntent's API coverage is uneven. Some clinical domains (clinical quality measures, risk scores) are well-exposed. Others (raw encounter data, lab results) require file-based extraction.
Universal Integration Patterns
Regardless of EHR vendor, these patterns apply:
# Idempotent FHIR resource upsert (works for all three vendors) def upsert_fhir_resource(resource: dict, warehouse_table: str): """ Upsert a FHIR resource into the bronze layer. Uses FHIR resource ID as the deduplication key. """ resource_id = resource.get("id") resource_type = resource.get("resourceType") last_updated = resource.get("meta", {}).get("lastUpdated") # Write to bronze table with FHIR resource ID as primary key # Downstream transformations handle normalization return { "fhir_resource_id": f"{resource_type}/{resource_id}", "resource_type": resource_type, "resource_json": json.dumps(resource), "last_updated": last_updated, "source_system": source_system, "ingested_at": datetime.utcnow().isoformat() }
Always validate HL7 messages before they enter your pipeline. Use the HL7 Parser to inspect message structure, verify segment completeness, and catch encoding issues before they propagate to your warehouse.
Key Takeaways
- FHIR Bulk Export is the correct mechanism for population-level historical data. FHIR REST API is for incremental and patient-specific queries. Never use REST API for full-population extraction.
- Epic and Cerner both support FHIR R4, but implementation details differ significantly. Test each endpoint independently — do not assume Epic behavior applies to Cerner.
- HL7 v2 ADT feeds are the most reliable real-time mechanism for clinical events. Do not replace them with FHIR subscriptions until your FHIR infrastructure has proven reliability equal to your existing MLLP setup.
- Cerner CCL direct extraction is faster than FHIR for bulk historical loads but requires credentialed Cerner access and carries schema-change risk.
- Validate HL7 message structure at the ingestion boundary with the HL7 Parser before messages enter your pipeline.
mdatool Team
The mdatool team builds free engineering tools for healthcare data architects, analysts, and engineers working across payer, provider, and life sciences data.
Related Guides
More in Data Architecture
TEFCA and Data Architecture: What Health Systems Need to Build Now
TEFCA is now operational. Qualified Health Information Networks are live. If your health system or payer is not actively planning TEFCA participation, you are behind. Here is what the architecture requires.
Read moreHealthcare Data Lakehouse Architecture: Building on Delta Lake for Payers
The data lakehouse pattern — combining the scalability of a data lake with the ACID guarantees of a warehouse — is a natural fit for payer data. Here is how to build it on Delta Lake, layer by layer.
Read moreReal-Time vs Batch Processing for Healthcare Claims: Architecture Decision Guide
Not every healthcare claims use case requires real-time processing — and treating them all the same wastes resources and adds complexity. Here is the decision framework for choosing the right architecture.
Read moreReady to improve your data architecture?
Free tools for DDL conversion, SQL analysis, naming standards, and more.