EHR Integration
Executive Summary
Clinical AI systems do not operate in isolation ā they operate as extensions of the Electronic Health Record (EHR), which is the authoritative system of record for every clinical data element a patient generates. The integration between a clinical AI system and the EHR is not an implementation detail that follows system design; it is a primary constraint that determines what data the AI can access, how patient identity is verified, what actions the AI can trigger, and how AI-generated content enters the medical record. This chapter covers the two dominant EHR integration standards (FHIR R4 and HL7 v2), the SMART on FHIR authorization framework, CDS Hooks as the primary clinical decision support integration pattern, and the architectural implications of each for clinical AI system design.
Learning Objectives
After reading this chapter, you will be able to:
- Design a clinical AI system's EHR integration using FHIR R4 APIs with appropriate SMART on FHIR authorization scopes
- Distinguish between HL7 v2 ADT feeds and FHIR R4 APIs and identify the correct integration mechanism for each clinical AI use case
- Implement a CDS Hooks integration that delivers AI recommendations at the appropriate point in the clinical workflow without requiring EHR customization
- Identify the technical constraints and failure modes of EHR integration that clinical AI architects must design around
Business Problem
Most healthcare AI value is unlocked only when the AI system has access to comprehensive patient context ā diagnoses, medications, lab results, vital signs, clinical notes ā and can deliver its output at the moment the clinician needs it, within the workflow the clinician is already using. Without EHR integration, clinical AI systems require clinicians to context-switch to a separate application, manually enter patient information, and then transfer results back to the EHR. This workflow friction is fatal to adoption.
The EHR integration problem has two sides. On the input side: how does the AI system get access to the comprehensive patient data it needs to generate high-quality output? On the output side: how does AI-generated content (a draft note, a recommendation, a risk score) appear in the clinician's EHR workflow, in the right place, at the right time, in a form that the EHR can store and retrieve?
Both sides are governed by standards ā FHIR, HL7 v2, SMART on FHIR, and CDS Hooks ā that were designed to solve exactly this problem. Understanding these standards is a prerequisite for clinical AI system design.
Why This Technology Exists
Before HL7 developed standards for health information exchange in the 1980s and 1990s, healthcare organizations exchanged patient data between systems through custom point-to-point interfaces ā each pair of communicating systems required a bespoke integration. As healthcare organizations grew to operate dozens of clinical systems (pharmacy, laboratory, radiology, cardiology, scheduling), the N² interface problem became untenable.
HL7 v2 (1987ā2003) established message-based standards for common clinical communications: patient admissions (ADT), laboratory orders and results (ORM, ORU), medication orders, radiology orders. HL7 v2 messages are pipe-delimited text messages transmitted over MLLP (a TCP-based minimal lower layer protocol), and they enabled a generation of clinical information system interoperability that was otherwise impossible.
HL7 FHIR (Fast Healthcare Interoperability Resources), developed from 2011 onward and reaching normative R4 status in 2019, represented a paradigm shift: instead of message-based event notification, FHIR defines a REST API for accessing discrete clinical data resources (Patient, Encounter, Observation, MedicationRequest, Condition, DiagnosticReport) using standard HTTP methods. The 21st Century Cures Act (2016) mandated FHIR R4 API support for certified EHR systems, making FHIR the primary integration mechanism for new clinical AI applications.
Conceptual Explanation
HL7 v2 vs. FHIR R4: Different Purposes
HL7 v2 and FHIR R4 are not competing standards; they address different integration scenarios:
- HL7 v2 ADT feeds: Real-time event notification. When a patient is admitted (ADT^A01), transferred (ADT^A02), or discharged (ADT^A03), an HL7 v2 message is sent to all subscribed downstream systems in near real-time. HL7 v2 is the mechanism for maintaining census information and triggering workflows based on patient location changes.
- FHIR R4 REST APIs: On-demand data retrieval. When an AI system needs the patient's current medication list, recent lab results, or active diagnoses, it queries the FHIR API. FHIR is pull-based; the requesting system asks for data when it needs it.
For clinical AI systems: HL7 v2 ADT feeds are used to trigger AI workflows based on clinical events (patient admitted ā start discharge planning AI background process). FHIR R4 APIs are used to retrieve the patient data the AI needs to do its work.
SMART on FHIR: Authorization for Clinical Applications
SMART on FHIR (Substitutable Medical Applications, Reusable Technologies on FHIR) is an authorization framework that allows external applications to obtain permission to access FHIR API data on behalf of a patient or clinician. It is built on OAuth 2.0 and OpenID Connect, with healthcare-specific extensions.
SMART on FHIR defines two launch contexts:
- EHR launch: The application is launched from within the EHR (e.g., the clinician clicks a button in the EHR that opens the AI application). The EHR passes the current patient and encounter context to the application.
- Standalone launch: The application is launched independently and requests authorization separately.
For clinical AI systems embedded in the EHR workflow, EHR launch is the preferred pattern: the AI system inherits the clinician's session context (patient ID, encounter ID) without the clinician needing to enter it.
Core Architecture
Components
FHIR R4 Resources for Clinical AI
| Resource | Clinical Content | AI Use Case |
|---|---|---|
| Patient | Demographics, identifiers, contact info | Patient context for discharge summary |
| Encounter | Visit type, dates, providers, disposition | Admission context for care coordination AI |
| Condition | Diagnoses, problems, clinical status | Clinical context for decision support |
| MedicationRequest | Ordered medications, dosages, status | Medication reconciliation, drug interaction alerts |
| Observation | Vital signs, lab results, scores | Early warning systems, care gap analysis |
| DiagnosticReport | Lab panels, radiology reports, pathology | Radiology AI integration, result notification |
| DocumentReference | Clinical notes, discharge summaries | Clinical documentation AI output delivery |
| Procedure | Clinical procedures performed | Coding assist, billing compliance |
| AllergyIntolerance | Patient allergies, reactions | Medication safety checks |
| CarePlan | Active care plans, goals | Care coordination AI |
SMART on FHIR Scopes
SMART on FHIR scopes follow the pattern {context}/{resource}.{permission}:
patient/Condition.readā Read the current patient's conditionspatient/MedicationRequest.readā Read the current patient's medication requestspatient/DocumentReference.writeā Write documents to the patient's recordlaunch/patientā Request access to the current patient contextlaunch/encounterā Request access to the current encounter contextoffline_accessā Request a refresh token for background processing
An AI system should request the minimum necessary SMART scopes for its use case ā scope minimization is the authorization analog of HIPAA's Minimum Necessary standard.
CDS Hooks
CDS Hooks is an HL7 standard that enables EHR-embedded clinical decision support without requiring EHR customization or vendor-specific plugins. A CDS Hooks integration consists of:
- Hook: A named clinical event in the EHR workflow (e.g.,
patient-view,order-sign,encounter-discharge) - CDS Service: An external REST service that the EHR calls when the hook fires, passing the current clinical context
- Card: A structured response the CDS service returns, rendered by the EHR as a recommendation, alert, or suggestion
Standard CDS Hooks used in clinical AI:
patient-viewā Fires when a clinician opens a patient chart. Used for: care gap identification, outstanding task alerts, risk score display.order-signā Fires when a clinician signs an order. Used for: drug-drug interaction alerts, drug-allergy alerts, dosing guidance.encounter-dischargeā Fires when a discharge workflow is initiated. Used for: discharge checklist, readmission risk score, follow-up gap detection.
Implementation Patterns
FHIR R4 Client for Clinical AI
# Educational Example ā FHIR R4 Client for Clinical AI
# Illustrates FHIR data retrieval pattern for clinical AI context assembly
# Not a production FHIR client ā omits retry logic, pagination, and error handling details
import json
from dataclasses import dataclass, field
from typing import Optional
import httpx
@dataclass
class ClinicalContext:
"""
Patient clinical context assembled from FHIR R4 resources.
Used as input to clinical AI use cases.
"""
patient_id: str
encounter_id: str
patient_name: Optional[str] = None
active_diagnoses: list[dict] = field(default_factory=list)
active_medications: list[dict] = field(default_factory=list)
recent_observations: list[dict] = field(default_factory=list)
encounter_details: Optional[dict] = None
class FHIRContextBuilder:
"""
Assembles clinical AI context from FHIR R4 API resources.
Requires a valid SMART on FHIR access token scoped to the patient.
"""
def __init__(self, fhir_base_url: str, access_token: str):
self.base_url = fhir_base_url.rstrip("/")
self.headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/fhir+json",
}
def build_discharge_summary_context(
self,
patient_id: str,
encounter_id: str,
) -> ClinicalContext:
"""
Retrieve the clinical context required for discharge summary generation.
Requests only the FHIR resources needed for this specific use case.
"""
ctx = ClinicalContext(patient_id=patient_id, encounter_id=encounter_id)
# Patient demographics (for personalized summary)
patient = self._get_resource("Patient", patient_id)
if patient:
name_entry = next(iter(patient.get("name", [])), {})
given = " ".join(name_entry.get("given", []))
family = name_entry.get("family", "")
ctx.patient_name = f"{given} {family}".strip() or None
# Active conditions for this encounter
conditions = self._search_resource(
"Condition",
params={
"patient": patient_id,
"encounter": encounter_id,
"clinical-status": "active",
"_sort": "-recorded-date",
"_count": "20",
},
)
ctx.active_diagnoses = self._extract_bundle_entries(conditions)
# Active medications
med_requests = self._search_resource(
"MedicationRequest",
params={
"patient": patient_id,
"status": "active",
"_sort": "-authored-on",
"_count": "30",
},
)
ctx.active_medications = self._extract_bundle_entries(med_requests)
# Recent vital signs and labs
observations = self._search_resource(
"Observation",
params={
"patient": patient_id,
"encounter": encounter_id,
"category": "vital-signs,laboratory",
"_sort": "-date",
"_count": "50",
},
)
ctx.recent_observations = self._extract_bundle_entries(observations)
return ctx
def _get_resource(self, resource_type: str, resource_id: str) -> Optional[dict]:
url = f"{self.base_url}/{resource_type}/{resource_id}"
try:
response = httpx.get(url, headers=self.headers, timeout=10.0)
response.raise_for_status()
return response.json()
except httpx.HTTPError:
return None
def _search_resource(self, resource_type: str, params: dict) -> Optional[dict]:
url = f"{self.base_url}/{resource_type}"
try:
response = httpx.get(
url, headers=self.headers, params=params, timeout=10.0
)
response.raise_for_status()
return response.json()
except httpx.HTTPError:
return None
def _extract_bundle_entries(self, bundle: Optional[dict]) -> list[dict]:
if not bundle or bundle.get("resourceType") != "Bundle":
return []
return [
entry["resource"]
for entry in bundle.get("entry", [])
if "resource" in entry
]CDS Hooks Service
# Educational Example ā CDS Hooks Service for Discharge AI
# Implements the encounter-discharge hook to deliver discharge summary assistance
# Educational disclaimer: not intended for clinical use
from dataclasses import dataclass
from typing import Any
from fastapi import FastAPI, Request
import httpx
app = FastAPI(title="Discharge Summary CDS Service")
@dataclass
class CDSCard:
"""A CDS Hooks response card rendered by the EHR."""
summary: str
indicator: str # "info" | "warning" | "critical"
detail: str
source_label: str
source_url: str
links: list[dict]
def build_launch_smart_app_card(
patient_id: str,
encounter_id: str,
ai_app_url: str,
) -> dict:
"""
Return a CDS card that launches the discharge summary AI application.
The SMART app launch carries the patient and encounter context.
"""
return {
"summary": "AI Discharge Summary Available",
"indicator": "info",
"detail": (
"An AI-assisted discharge summary draft is available for this encounter. "
"Click to review and complete the discharge summary in the AI tool."
),
"source": {
"label": "Clinical AI Platform",
"url": ai_app_url,
},
"links": [
{
"label": "Open Discharge Summary AI",
"url": f"{ai_app_url}/launch?patient={patient_id}&encounter={encounter_id}",
"type": "smart",
}
],
}
@app.get("/cds-services")
def discovery():
"""CDS Hooks discovery endpoint ā EHR calls this to find available services."""
return {
"services": [
{
"hook": "encounter-discharge",
"title": "Discharge Summary AI Assistant",
"description": "Offers AI-assisted discharge summary generation when a discharge workflow is initiated.",
"id": "discharge-summary-ai",
"prefetch": {
"patient": "Patient/{{context.patientId}}",
"encounter": "Encounter/{{context.encounterId}}",
},
}
]
}
@app.post("/cds-services/discharge-summary-ai")
async def discharge_summary_hook(request: Request):
"""
Handles the encounter-discharge CDS Hook.
Returns a card that launches the discharge summary AI application.
"""
body = await request.json()
context = body.get("context", {})
patient_id = context.get("patientId", "")
encounter_id = context.get("encounterId", "")
card = build_launch_smart_app_card(
patient_id=patient_id,
encounter_id=encounter_id,
ai_app_url="https://ai-platform.internal/discharge-summary",
)
return {"cards": [card]}Enterprise Considerations
EHR Vendor API Limits: Commercial EHR platforms (Epic, Oracle Health, Meditech) impose rate limits on their FHIR APIs. A clinical AI system that generates high-volume FHIR queries (e.g., pulling patient context for every active inpatient on the census every 15 minutes) will hit these limits. Design AI systems to cache FHIR data appropriately and request data on-demand rather than in bulk polling patterns.
Epic App Orchard Registration: For AI applications integrated with Epic via SMART on FHIR, the application must be registered in Epic's App Orchard (or via local developer account for in-house applications). This registration process determines which SMART scopes the application can request and requires Epic review for production deployment.
HL7 v2 Message Reliability: HL7 v2 messages are transmitted over MLLP, which uses TCP acknowledgments but has no built-in message persistence or replay. In production environments, HL7 v2 interfaces require an integration engine (Rhapsody, Mirth Connect, Microsoft Azure Health Data Services HL7 ingestion) that adds message queuing, deduplication, and retry.
FHIR Subscription for Real-Time Events: FHIR R4 introduced Subscriptions ā a mechanism for the EHR to push notifications to a clinical AI system when specific clinical events occur (e.g., a lab result is received, a patient is discharged). FHIR Subscriptions provide a REST-based alternative to HL7 v2 ADT feeds for event-driven AI workflows.
Security Considerations
- SMART on FHIR tokens must be scoped to the minimum necessary resources ā a discharge summary AI application should not hold a scope for reading all patients' records
- SMART on FHIR access tokens have short lifetimes (typically 1 hour); long-running AI workflows must use refresh tokens (
offline_accessscope) or require re-authentication - CDS Hook services receive patient data in the prefetch payload ā these services are PHI receivers requiring BAA coverage and the same security controls as other PHI-handling components
- FHIR endpoints must be protected: all FHIR API calls from AI systems must go through the AI gateway (not direct from application code) to maintain centralized audit logging
Healthcare Example
Educational Example ā Illustrative Workflow. Not intended for clinical decision making.
The Reference Healthcare Organization deploys three EHR integration patterns for its clinical AI platform:
Pattern 1 ā ADT-Triggered Background Processing: When a patient is admitted (ADT^A01), the integration engine forwards the HL7 v2 message to the AI platform's admission event handler. The AI platform retrieves the patient's FHIR data, begins assembling the context that will be needed for discharge summary generation, and caches it with a 48-hour TTL. When the discharge workflow is triggered, the context is already assembled ā reducing the AI response latency at the moment of use.
Pattern 2 ā SMART on FHIR EHR Launch: The discharge summary AI application is registered as a SMART on FHIR application in the EHR. Clinicians see a "Generate AI Draft" button in the discharge documentation workflow. Clicking it initiates a SMART EHR launch, passing the patient and encounter context. The AI application retrieves the fresh FHIR data and presents the draft within the EHR encounter window.
Pattern 3 ā CDS Hooks Prior Auth: The prior authorization AI is exposed as a order-sign CDS Hook service. When a physician signs a referral or procedure order that commonly requires prior authorization, the EHR fires the hook. The CDS service checks the payer's prior auth requirements for the procedure code and returns a card indicating whether prior auth is required and pre-populating the AI-assisted prior auth request form.
Common Mistakes
Polling FHIR Instead of Using Subscriptions or ADT Feeds. Organizations that poll FHIR APIs on a fixed interval to detect patient status changes hit EHR API rate limits, add unnecessary load to the EHR, and receive stale data between polls. Use HL7 v2 ADT feeds or FHIR Subscriptions for event-driven workflows.
Requesting Broad SMART Scopes. An AI application that requests patient/*.read (read all FHIR resources for the patient) to avoid managing specific scopes creates unnecessary PHI exposure. EHR platforms increasingly reject overly broad scope requests in the app review process.
Writing AI Output Outside the EHR. AI-generated clinical content (discharge summaries, clinical notes) must be stored in the EHR as the authoritative medical record. Organizations that store AI output in a separate AI platform database create a fragmented medical record that does not meet Joint Commission completeness standards.
Missing HL7 v2 Acknowledgment Handling. HL7 v2 senders expect an Application Acknowledgment (AA) or Application Error (AE) response for every message. AI systems that consume HL7 v2 messages without sending proper acknowledgments cause the sending system to retry, creating duplicate messages.
Best Practices
- Use FHIR R4 APIs for data retrieval; use HL7 v2 ADT feeds or FHIR Subscriptions for event notification
- Request minimum necessary SMART on FHIR scopes ā one resource type at a time where possible
- Cache FHIR data appropriately (patient context changes infrequently within an encounter) to reduce EHR API load
- Write AI-generated clinical content back to the EHR via FHIR DocumentReference ā keep the medical record complete and authoritative
- Register AI applications through the EHR vendor's official app registration process (Epic App Orchard for Epic environments) rather than using developer credentials in production
- Implement CDS Hooks for EHR-integrated recommendations ā it is the standard-based alternative to EHR vendor customization
Alternatives
| Integration Pattern | When to Use | Complexity | EHR Support |
|---|---|---|---|
| FHIR R4 REST API | Data retrieval for any AI use case | Low-Medium | Mandated for certified EHRs |
| HL7 v2 ADT feeds | Real-time patient event notification | Medium | Universal |
| CDS Hooks | In-workflow clinical decision support | Medium | Growing (Epic, Cerner) |
| SMART on FHIR | EHR-launched AI application authorization | Medium | Mandated for certified EHRs |
| Epic Hyperdrive (native) | Deep Epic integration, Epic-hosted deployment | High | Epic only |
| Direct database access | Legacy integration, reporting | Low (technical) | Not recommended; bypasses security |
Interview Questions
Q: A clinical AI system needs to retrieve a patient's active medications list when a physician opens a prescription order. Design the EHR integration.
Category: System Design Difficulty: Senior Role: AI Architect / Healthcare AI Engineer
Answer Framework:
This is a CDS Hooks use case with FHIR R4 data retrieval. When the physician initiates the prescription order workflow, the EHR fires the order-sign hook (or medication-prescribe if the EHR supports it). The AI application, registered as a CDS Hooks service, receives the hook call with the prefetch payload.
The prefetch payload should include the patient's active MedicationRequest resources ā declare the prefetch template in the CDS discovery endpoint so the EHR populates it without a separate API call from the CDS service. This avoids an extra round-trip to the FHIR server at hook-fire time, which would add latency during the prescription workflow.
The CDS service then processes the medication list, checks for drug-drug interactions or allergy conflicts with the new prescription being signed, and returns a CDS Card if an issue is identified. The card should display the specific interaction, the clinical severity (informational vs. warning vs. critical), and a suggested alternative if applicable.
Key design considerations: the CDS service must respond within the EHR's timeout window (typically 5 seconds) ā LLM inference should only be invoked if needed for complex interaction reasoning, not for simple lookup-based checks that can be answered from a drug interaction database without LLM. The system must also implement circuit breaker logic: if the CDS service is unavailable, the EHR must be able to proceed without the CDS response (clinical workflow cannot block on external AI service availability).
Key Points to Hit:
order-signCDS Hook as the trigger- Prefetch payload for the medication list (avoids extra latency from a separate FHIR call)
- 5-second response time constraint ā consider whether LLM is needed or simpler lookup suffices
- Circuit breaker: CDS service unavailability must not block clinical workflow
Key Takeaways
- FHIR R4 and HL7 v2 are complementary standards: FHIR for data retrieval, HL7 v2 ADT for real-time event notification
- SMART on FHIR authorizes AI applications to access FHIR data on behalf of specific patients within specific contexts ā always request minimum necessary scopes
- CDS Hooks is the standard-based mechanism for embedding AI recommendations in the EHR workflow without EHR vendor customization
- Clinical AI output (notes, summaries) must be written back to the EHR via FHIR DocumentReference ā keeping the medical record complete and authoritative
- EHR API rate limits are a real production constraint; design AI systems to cache FHIR data and avoid polling patterns
- CDS Hook services must respond within EHR timeout windows and must implement circuit breakers ā clinical workflow cannot be blocked by an unavailable AI service
Glossary
FHIR (Fast Healthcare Interoperability Resources): An HL7 standard for health data exchange using modern web technologies (REST, JSON, HTTP). FHIR R4 is the normative release mandated for certified EHR systems by the 21st Century Cures Act.
SMART on FHIR: A security authorization framework built on OAuth 2.0 that allows external applications to access FHIR APIs on behalf of patients or clinicians with explicit consent.
CDS Hooks: An HL7 standard that allows EHRs to call external web services at specific clinical workflow events and display the returned recommendations as cards in the EHR interface.
HL7 v2: A message-based standard for clinical data exchange. ADT (Admit, Discharge, Transfer) messages communicate patient location and status changes in near real-time.
MLLP (Minimum Lower Layer Protocol): A TCP-based protocol used to transport HL7 v2 messages between healthcare systems.
DocumentReference: A FHIR resource that references a clinical document (note, summary, report) stored in the EHR, with metadata describing the document type, author, and clinical context.
Further Reading
- Chapter 2: HIPAA and AI ā PHI handling for FHIR API data in AI inference pipelines
- Chapter 4: Clinical RAG ā Using FHIR-retrieved clinical data as RAG context
- Chapter 6: HMS Reference Architecture ā Complete HMS EHR integration architecture
- HL7 FHIR R4 Specification ā Official FHIR R4 specification
- SMART on FHIR Documentation ā SMART authorization framework specification
- CDS Hooks Specification ā Official CDS Hooks standard