Skip to content

Connect Epic to AI Agents: Automate FHIR Data and Clinical Ops

Uday Gajavalli Uday Gajavalli 11 min read AI & Agents
TrutoFor teams building AI agents

Give your AI agent Epic tools.

A technical guide to connecting Epic to AI agents via Truto's /tools endpoint. Discover how to handle Epic FHIR R4 quirks, map native tools to LLM frameworks, and orchestrate complex clinical data workflows.

In this guide

  1. 01Define Agent Framework Strategy
  2. 02Fetch Epic Tools via Truto
  3. 03Bind Tools to the LLM
  4. 04Implement Retry and Backoff
  5. 05Execute Clinical Workflows

The guide

Learn how to connect Epic to AI agents using Truto's /tools endpoint. Bypass FHIR quirks and bind Epic operations directly to your LLM workflows.

You want to connect Epic to an AI agent so your system can independently read FHIR data, generate clinical notes, draft service requests, and orchestrate patient workflows based on historical health context. Here is exactly how to do it using Truto's /tools endpoint and SDK, bypassing the need to build a custom Electronic Health Record (EHR) integration from scratch.

Giving a Large Language Model (LLM) read and write access to your Epic instance is a high-stakes engineering endeavor. You either spend months building, certifying, and maintaining a custom SMART on FHIR connector, or you use a managed infrastructure layer that handles the authentication boilerplate, schema mapping, and endpoint discovery for you. If your team uses ChatGPT, check out our guide on connecting Epic to ChatGPT, or if you are building on Anthropic's models, read our guide on connecting Epic to Claude. For developers building custom autonomous clinical workflows, you need a programmatic way to fetch these tools and bind them directly to your agent framework.

This guide breaks down exactly how to fetch AI-ready tools for Epic, bind them natively to an LLM using LangChain (or any framework like LangGraph, CrewAI, or the Vercel AI SDK), and execute complex clinical operations workflows. For a deeper look at the architecture behind this approach, refer to our research on architecting AI agents and the SaaS integration bottleneck.

Why Direct EHR Integrations Fail AI Agents

Before writing a line of integration code, you must decide what layer your agent will talk to. When handling Protected Health Information (PHI), this choice dictates not only development velocity but the clinical safety of your production system.

Direct API tools (exposing raw Epic FHIR R4 endpoints directly to an LLM) look convenient on paper. In practice, they push extreme provider-specific quirks into the LLM's finite context window. The model has to remember that Epic search queries require specific demographic combinations, that pagination uses a _count parameter instead of a standard limit, and that responses are nested heavily inside a FHIR Bundle. Every one of those quirks is a hallucination waiting to happen.

Using Truto's unified tool layer collapses the complexity of the Epic FHIR API. Your agent sees deterministic functions with flat, structured parameters. That gives you three concrete safety wins:

  1. Smaller attack surface for hallucination. The LLM chooses from highly specific, stable function names with explicit parameter constraints. It does not invent URL query strings or guess at FHIR resource structures.
  2. Deterministic input validation. Every tool has a strict JSON schema. If the agent hallucinates an invalid argument, the tool request is rejected locally before it ever hits the Epic server, failing fast.
  3. Context window preservation. Instead of burning tokens teaching the LLM how to parse a raw Epic FHIR Bundle with nested entry.resource objects, the LLM receives normalized, directly usable data.

The Engineering Reality of the Epic FHIR API

Giving an LLM access to external data sounds simple in a prototype. You write a Node.js function that makes a fetch request and wrap it in an @tool decorator. In production against complex healthcare systems like Epic, standard REST assumptions collapse.

If you hardcode these interactions into your agent, you will spend your sprints writing defensive FHIR parsing logic instead of improving your model's reasoning.

The FHIR Search Parameter Trap

Standard REST APIs allow broad list endpoints (e.g., GET /users). Epic fundamentally rejects this. To protect patient privacy and system performance, Epic requires strict search criteria for almost all clinical endpoints.

For example, you cannot just list patients. The Epic API demands at least one primary identifier or a highly specific demographics combination (e.g., family name plus birthdate, or name plus gender). If a search matches more than 100 patients, the API intentionally fails the request. If you expose raw FHIR endpoints to an LLM, the model will frequently attempt naked searches and crash your workflow. Truto's tool schemas enforce these requirements at the parameter level, ensuring the LLM understands it must supply the required demographic parameters before executing the tool call.

The Prefer Header Requirement for Write Operations

When an AI agent wants to draft a new record in Epic (such as an AllergyIntolerance or a ServiceRequest), the standard HTTP POST behavior creates a massive blind spot. By default, Epic responds to a successful creation with an HTTP 201 Created status and a Location header containing the new ID. The body is entirely empty.

Agents despise empty responses. If an LLM creates a record and gets nothing back, it often assumes failure and initiates a retry loop, resulting in duplicate clinical records. To fix this, you must pass the exact HTTP header Prefer: return=representation so Epic returns the full created FHIR resource. Truto abstracts this completely - write operations exposed via the /tools endpoint automatically enforce these headers and normalize the response, giving the LLM immediate confirmation of its action.

The _search POST Interaction for Long Queries

Clinical agents often generate massive queries when pulling historical patient context. When querying DocumentReference or Condition resources with multiple SNOMED or LOINC codes, the resulting URL can exceed standard HTTP limits. Epic supports the FHIR _search interaction, which allows you to pass query parameters in an HTTP POST form body instead of the query string.

Teaching an LLM when to switch between a GET request and a POST _search request based on character count is an exercise in futility. Truto handles this translation at the Proxy API level. The agent calls a single search tool, and the underlying infrastructure orchestrates the correct HTTP method based on the payload size.

Handling Epic Rate Limits at Scale

Clinical agents are incredibly noisy. An autonomous loop tasked with summarizing a patient's chart will independently query demographics, vitals, active medications, care plans, and recent encounters within milliseconds. Epic will aggressively rate limit this behavior to protect EHR stability.

Factual note on how Truto handles this: Truto does not absorb, queue, or silently retry rate limit errors. When the upstream Epic API returns an HTTP 429 Too Many Requests, Truto passes that error directly back to the caller.

However, Truto normalizes the chaotic upstream rate limit information into a standardized set of headers based on the IETF specification. Regardless of how Epic formats its rate limit headers, you will always receive:

  • ratelimit-limit: The total requests allowed in the current window.
  • ratelimit-remaining: The number of requests left before throttling.
  • ratelimit-reset: The exact Unix timestamp when your quota resets.

Your agent execution loop is fully responsible for intercepting these 429s, reading the ratelimit-reset header, and implementing a backoff/sleep mechanism before allowing the LLM to continue.

Epic Hero Tools for AI Agents

When you call the Truto /tools endpoint for an authenticated Epic integration, you gain access to a massive inventory of FHIR R4 operations. Do not dump all of these into your agent's context window. Select the exact tools required for your clinical workflow.

Here are the highest-leverage tools available for Epic automation.

Search Epic Patients (list_all_epic_patients)

This is the entry point for almost all clinical workflows. This tool searches patients in Epic using the FHIR Patient resource. Because Epic restricts open searches, the agent must supply demographic parameters (like family name and birthdate) to isolate a specific patient. The tool automatically resolves pagination via the FHIR Bundle next link.

Example Agent Prompt: "Find the patient record for Robert Jenkins, born May 12, 1978. Retrieve his FHIR ID so we can pull his clinical history."

List Clinical Observations (list_all_epic_observations)

This tool retrieves patient observations, which cover vital signs, lab results, and social history. The agent must supply a patient ID and either a category or a specific LOINC code. This is essential for agents tasked with generating pre-encounter summaries or tracking health trends.

Example Agent Prompt: "Using patient ID 'e-8X...', retrieve all observations categorized as 'vital-signs' from the last 30 days and summarize the blood pressure trends."

Create a Service Request (create_a_epic_service_request)

This tool allows the agent to draft a new ServiceRequest in Epic from a structured JSON body. This covers orders, outside records requests, and intervention planning. Truto ensures the Prefer: return=representation header is sent so the agent receives the generated record back immediately.

Example Agent Prompt: "Draft a service request for an outpatient MRI of the lower back for patient 'e-8X...'. Set the intent to 'order' and the status to 'draft'."

Search Document References (list_all_epic_document_references)

This tool searches DocumentReferences in Epic, providing access to clinical notes, CCDAs, and narrative results. The agent can filter by category and type to isolate specific documentation, enabling deep semantic analysis of historical physician notes.

Example Agent Prompt: "Pull all clinical notes categorized as 'discharge-summary' for patient 'e-8X...' and extract the primary discharge instructions."

Get Single Encounter by ID (get_single_epic_encounter_by_id)

This tool fetches a precise Epic Encounter by its FHIR ID. It returns granular data including admission status, episode of care context, and associated diagnoses.

Example Agent Prompt: "Fetch the encounter details for ID 'enc-40291'. Identify the class of the encounter and the primary attending practitioner."

List Medication Requests (list_all_epic_medication_requests)

This tool queries active, completed, or drafted medication orders for a specific patient. It enables agents to cross-reference active prescriptions against newly proposed care plans to flag potential conflicts.

Example Agent Prompt: "List all active medication requests for patient 'e-8X...'. Check if there are any current orders for ACE inhibitors."

For the complete inventory of Epic capabilities—including Adverse Events, Care Plans, Immunizations, and Bulk Exports—visit the Epic integration page.

Building Multi-Step Workflows

To build a resilient AI agent, you need an orchestration loop that can fetch Epic tools via the Truto API, bind them to the LLM, and explicitly handle HTTP 429 rate limit responses.

Because Truto exposes tools via standard HTTP REST definitions, this approach is entirely framework agnostic. You do not have to rely strictly on the Model Context Protocol (MCP). Here is how you construct a robust agent loop using LangChain and the Truto SDK, complete with rate limit handling.

sequenceDiagram
    participant App as Your Agent App
    participant Truto as Truto API
    participant Epic as Epic API

    App->>Truto: GET /integrated-account/<id>/tools
    Truto-->>App: Returns JSON schemas for tools
    App->>App: LLM generates tool call (e.g., list_all_epic_patients)
    App->>Truto: Execute tool call
    Truto->>Epic: FHIR R4 API request
    Epic-->>Truto: Nested FHIR Bundle
    Truto-->>App: Normalized flat JSON array
    App->>App: LLM analyzes data, decides next action

Here is a TypeScript implementation of this architecture. Notice how the execution logic actively monitors the ratelimit-reset header when an error occurs.

import { ChatOpenAI } from "@langchain/openai";
import { TrutoToolManager } from "truto-langchainjs-toolset";
import { AgentExecutor, createOpenAIToolsAgent } from "langchain/agents";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
 
async function runEpicAgent() {
    // 1. Initialize the LLM
    const llm = new ChatOpenAI({ 
        modelName: "gpt-4o", 
        temperature: 0 
    });
 
    // 2. Initialize Truto and fetch Epic tools
    // The ToolManager connects to Truto's /tools endpoint automatically
    const trutoManager = new TrutoToolManager({
        accessToken: process.env.TRUTO_API_KEY!,
        integratedAccountId: process.env.EPIC_ACCOUNT_ID!
    });
 
    // Filter tools to provide the agent with a focused clinical scope
    const tools = await trutoManager.getTools({
        methods: ["read", "create"]
    });
 
    // 3. Define the Agent Prompt
    const prompt = ChatPromptTemplate.fromMessages([
        ["system", "You are a highly capable clinical assistant with access to Epic FHIR tools. You must retrieve patient data carefully. If a tool fails, evaluate the error and try again with corrected parameters. Never guess patient IDs."],
        ["human", "{input}"],
        new MessagesPlaceholder("agent_scratchpad"),
    ]);
 
    // 4. Bind Tools and Create Agent
    const agent = await createOpenAIToolsAgent({
        llm,
        tools,
        prompt
    });
 
    const executor = new AgentExecutor({
        agent,
        tools,
        maxIterations: 10
    });
 
    // 5. Execute the Workflow with Rate Limit Handling
    const userInput = "Find the patient record for Sarah Connor (born 1985-08-29) and summarize her active medication requests and recent vital signs.";
 
    try {
        const result = await executor.invoke({ input: userInput });
        console.log("Agent Output:", result.output);
    } catch (error: any) {
        // Truto passes the HTTP 429 directly to the caller. 
        // You are responsible for inspecting the standardized headers and retrying.
        if (error.status === 429) {
            const resetTime = error.headers['ratelimit-reset'];
            console.error(`Epic Rate limit hit. Quota resets at Unix Timestamp: ${resetTime}. Pausing agent execution.`);
            // Implement your queue/sleep logic here based on resetTime
        } else {
            console.error("Agent execution failed:", error);
        }
    }
}
 
runEpicAgent();

This execution environment isolates the LLM from Epic's XML/JSON payload variations, demographic search traps, and deep FHIR bundling, allowing the model to focus purely on clinical reasoning.

Workflows in Action

When you give an LLM determinist access to Epic via Truto, you unlock autonomous workflows that previously required massive human intervention. Here is what happens when you combine the hero tools outlined above.

Scenario 1: Patient Vitals Summary and Clinical Note Drafting

A triage nurse needs a pre-encounter summary before seeing a patient for a follow-up visit. They trigger the AI agent via a simple text interface.

User Prompt: "Find the patient record for Marcus Wright, DOB 1975-02-14. Pull his last 3 blood pressure observations and his latest discharge summary note. Draft a short pre-encounter briefing."

Agent Execution Steps:

  1. The agent calls list_all_epic_patients passing {"name": "Wright", "birthdate": "1975-02-14"} to retrieve the correct FHIR ID.
  2. Using that ID, it calls list_all_epic_observations with {"category": "vital-signs", "patient": "<id>"} and isolates the blood pressure data.
  3. It then calls list_all_epic_document_references with {"category": "clinical-note", "patient": "<id>"} to fetch the discharge summary.
  4. Finally, the LLM analyzes all the retrieved payloads and generates a concise paragraph summarizing Marcus's recent blood pressure trends and the instructions from his last discharge, returning the clean text to the nurse.

Scenario 2: Pre-Encounter Prep & Lab Order Generation

A care coordinator needs to prepare a standard set of lab orders for a diabetic patient based on their recent history, queuing them up as drafts for the physician to approve.

User Prompt: "Look up patient Jane Smith (DOB 1960-11-03). Check if she has had an HbA1c observation in the last 6 months. If not, draft a ServiceRequest for an HbA1c lab order."

Agent Execution Steps:

  1. The agent executes list_all_epic_patients using Jane's demographics to acquire her ID.
  2. It runs list_all_epic_observations targeting specific LOINC codes for HbA1c to check the historical timeline.
  3. Upon discovering the last test was 8 months ago, the agent decides action is required.
  4. It executes create_a_epic_service_request, crafting a JSON payload with the intent set to order, status set to draft, and the specific clinical code for an HbA1c lab.
  5. Because Truto normalizes the Prefer: return=representation header, the agent receives the successfully created ServiceRequest ID immediately and reports back to the coordinator: "Draft ServiceRequest #90281 created for physician review."

Automate Epic Operations Without Building EHR Infrastructure

Building an AI agent that can securely and reliably interact with Epic is not an LLM prompting problem; it is a complex systems integration problem. Direct FHIR API calls push rate limits, deep pagination layers, and complex search restrictions directly into the LLM's context, resulting in severe hallucinations and broken clinical workflows.

By leveraging Truto's /tools endpoint, you abstract away Epic's architectural friction. Your agent interacts with highly constrained, deterministic JSON schemas, significantly reducing error rates while unlocking fully autonomous clinical and administrative operations.

Two ways to put Epic to work

Truto

For product teams

Give your agent Epic tools

Your customers connect their own Epic accounts. Your product gets one API and MCP tools for Epic, through Truto.

FAQ

How do I handle rate limits when connecting Epic to AI agents?
Truto does not absorb or automatically retry rate limit errors. When the Epic API returns an HTTP 429, Truto passes the error to your agent, standardizing the response into IETF rate limit headers (`ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`). Your application logic must handle the backoff and retry mechanism.
Can I use any LLM framework to connect to Epic?
Yes. The Truto `/tools` endpoint exposes Epic capabilities as standardized JSON schemas. You can consume these schemas in any framework, including LangChain, LangGraph, CrewAI, or the Vercel AI SDK, to execute autonomous clinical workflows.
Do I need to understand FHIR to use Epic AI agent tools?
While foundational clinical knowledge helps, Truto abstracts away the severe complexities of the FHIR R4 JSON structure. Your agent interacts with deterministic, flat tools (e.g., `list_all_epic_patients`) rather than manually constructing nested FHIR Bundles or managing `Prefer: return=representation` headers.
Epic EpicAI agent tools Get a sandbox

More from our Blog