The three patterns you actually have to choose between
Every conversation about "integrating an AI agent with our EHR" reduces to the same three patterns. Pick the wrong one, and you spend six months fighting the integration before the agent has done any useful work. This piece is for the engineering lead and the integration architect who are sizing the build. It assumes you have read the pillar guide and have a working understanding of FHIR.
Pattern 1 — FHIR R4 REST API
Modern EHR vendors expose FHIR R4 endpoints over HTTPS. The agent reads, searches, and writes structured resources. Authentication is OAuth2, often via SMART on FHIR. Latency is 100–500ms per call, and the resource model is standardized — you can carry the same agent code across Epic, Cerner, athena, and modern open-source FHIR servers with mostly configuration-level changes.
This is the cleanest path forward. It is also the path with the most vendor-specific extensions and quirks. system/*.read scope on Epic does not behave identically to the same scope on Oracle Health. Plan for adapter layers per vendor, even when both speak FHIR R4. See we built a FHIR R4 server from scratch for the deep mechanics.
Pattern 2 — HL7 v2 over an interface engine
Most production hospitals still run on Mirth, Rhapsody, or a vendor-specific equivalent. HL7 v2 messages route through the engine; the agent subscribes to the relevant feeds (ADT, ORM, ORU, SIU, etc.) and listens for events.
This pattern is robust and well-understood — Mirth has been in production since 2007 — but the parsing is pipe-delimited and there is no standard write API. You read events, you do not push state back. For agents that need to react to events (a new admission, a discharge, a lab result returning) and trigger downstream work, this is often the right choice. We expand on this in interoperability standards in healthcare: a guide to FHIR, HL7, and more.
Pattern 3 — CDS Hooks plus SMART on FHIR
Triggered at clinician decision points inside the EHR — opening a chart, ordering a medication, signing a note. The agent gets a synchronous request, has under a second to respond, and surfaces a card or suggestion to the clinician without leaving the EHR UI.
This pattern wins on UX and clinical workflow context. It loses on execution scope: you cannot run a long-form workflow inside a CDS Hook. Use it for inline assistance — flagging missing documentation, suggesting a code, surfacing a relevant lab — not for end-to-end workflows. See AI enters the hospital with CDS Hooks, not a dashboard.
What does "EHR integration" actually mean for an AI agent?
For an AI agent, EHR integration is the surface across which the agent reads patient data, writes structured resources back, and subscribes to clinical events. It is more than a one-time data export — agents need bidirectional, real-time, scoped access. Three integration patterns dominate the modern EHR landscape: FHIR R4 REST (structured, bidirectional), HL7 v2 over an interface engine (event-driven, robust on legacy), and CDS Hooks plus SMART on FHIR (embedded inside the clinician's workflow). Most production deployments use all three. The architecture decision is which one you build first.
FHIR resource cheat sheet by use case
Notice the recurring resources: Patient, Encounter, Coverage, Observation, Task, Communication. These five carry most of the weight in administrative agents. Clinical agents add Condition, MedicationRequest, DiagnosticReport, and CarePlan. Subscription patterns matter most for event-driven workflows — eligibility recheck on Coverage update, claim status on Task transition.
SMART on FHIR auth — the four steps that go wrong
Step 1 — Discovery
Hit /.well-known/smart-configuration from the FHIR base URL. You get the token endpoint, supported scopes, and capabilities. Do not cache the discovery document for a long time — vendors change endpoints and scope advertisements more often than they should. Pitfall: caching for 24 hours and pinning to a deprecated scope.
Step 2 — Backend auth
For a server-side agent, use the JWT-signed assertion grant: build a JWT, sign with your private key, POST to the token endpoint, get back an access token. Pitfall: wrong scope shape — Epic uses system/Patient.read, others use patient/*.read; mismatch fails silently or returns 403 with no body. Also: rotate your JWK before it expires, not after.
Step 3 — Resource calls
Use the bearer token. Set Accept: application/fhir+json. Page through search results with _count and follow the next link in the bundle. Pitfall: treating a 404 on a resource read as a data error rather than a permission or scope error.
Step 4 — Refresh
Token lifetimes vary — 5 minutes to an hour. Refresh proactively, not reactively. Pitfall: infinite refresh loops when refresh fails — bound the retries explicitly. Also: implement replay protection for assertion-based grants. For deeper coverage of these failure modes, read SMART on FHIR authentication: 6 things that break.
Build skeleton — the agent calling FHIR
# fhir_client.py — minimal SMART on FHIR client an agent will call
import requests, jwt, time
class FHIRClient:
def __init__(self, base_url, client_id, private_key, kid):
self.base = base_url
self.client_id = client_id
self.pk = private_key
self.kid = kid
self.token = None
self.token_exp = 0
def _assertion(self, token_url):
return jwt.encode({
"iss": self.client_id, "sub": self.client_id,
"aud": token_url, "exp": int(time.time()) + 60,
"jti": str(uuid.uuid4()),
}, self.pk, algorithm="RS384", headers={"kid": self.kid})
def _refresh(self):
cfg = requests.get(f"{self.base}/.well-known/smart-configuration").json()
r = requests.post(cfg["token_endpoint"], data={
"grant_type": "client_credentials",
"scope": "system/*.read system/*.write",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": self._assertion(cfg["token_endpoint"]),
})
r.raise_for_status()
body = r.json()
self.token = body["access_token"]
self.token_exp = time.time() + body.get("expires_in", 300) - 30
def call(self, method, path, **kw):
if time.time() >= self.token_exp:
self._refresh()
headers = kw.pop("headers", {})
headers["Authorization"] = f"Bearer {self.token}"
headers["Accept"] = "application/fhir+json"
return requests.request(method, f"{self.base}{path}", headers=headers, **kw)
This is intentionally small. Production additions: connection pooling, exponential backoff with jitter on 429s, structured logging with FHIR resource types, an audit hook for HIPAA logging, and a circuit breaker per upstream EHR. The agent's planner code never touches the FHIR client directly — it calls a small set of named tools that wrap this client.
Choosing between the patterns
Three questions decide it:
- Does the EHR expose FHIR R4 APIs with both read and write? If yes, and you are greenfield, default to Pattern 1. If write is read-only, default to a hybrid (FHIR for reads, HL7 v2 events for state changes).
- Is the workflow event-driven? A discharge triggers a downstream task; a lab result kicks off an alert. If yes, you need Pattern 2 (event subscription) somewhere in your stack.
- Does the workflow have to live inside the EHR UI? Inline clinician guidance at a decision point is Pattern 3. Anything that needs a separate workspace or longer reasoning loop is not.
Most production deployments run all three. The agent reads via FHIR, subscribes to events via the interface engine, and surfaces inline assistance via CDS Hooks. The decision is which one you build first and which ones you defer.
Real-world example
Geisinger's published work on agentic eligibility verification used FHIR R4 plus payer 270/271 EDI — the cleanest possible production architecture for the use case. Mass General Brigham's ambient documentation deployment uses SMART on FHIR launches inside Epic, with the agent operating in a session-bound context that the clinician initiated. Mayo's prior-auth automation work uses FHIR for chart access plus a payer adapter layer above the portals. The three patterns above are not academic — they are the architecture choices behind every production agent that is publicly documented.
Key takeaways
- Default to FHIR R4 if you can. Modern, structured, portable across vendors — the cleanest path forward.
- Use HL7 v2 for event-driven flows. Discharges, admissions, and lab results are still cleanest over the interface engine.
- Use CDS Hooks for inline clinical assistance. Sub-second response, native UX, limited execution scope — the right tool for the right job.
- SMART on FHIR auth has four steps and four pitfalls. Discovery caching, scope shape, resource calls, and refresh — everyone fails differently.
- Build a vendor adapter layer. Same agent code, different EHR — only the adapter changes when you onboard a new system.
Call to Action
Want to build an AI Agent for your healthcare product? Get in touch with our team for a working session — we will scope the architecture, integration patterns, and 90-day plan against your own systems.
Learn more about AI Agents in Healthcare → read the full pillar guide.
Related reading:
- Also read: Building AI Agents with FHIR APIs: A Practical Overview
- Also read: How to Build an AI Agent for Patient Intake Automation



