AI agents insurance eligibility verification is now a baseline expectation for health-tech platforms that want to cut front-desk friction. This guide walks through building a real-time verification agent that calls payer FHIR endpoints, normalizes the 271 response, and uses an LLM only to interpret unstructured denial text. You will end up with a deployable service that returns a deterministic eligibility verdict within a few hundred milliseconds.
Step 1: Define the eligibility data contract
Before writing agent logic, lock down what “eligible” means for your system. Payers return X12 271 EDI or FHIR CoverageEligibility responses that are verbose and nested. Your agent should reduce that to a flat contract so downstream scheduling and billing code never parses payer-specific shapes.
{
"member_id": "string",
"payer_id": "string",
"service_type": "string",
"eligible": "boolean",
"copay": "number | null",
"deductible_remaining": "number | null",
"effective_date": "string",
"termination_date": "string | null",
"note": "string | null",
"raw_source": "fhir"
}
This contract becomes the agent’s single source of truth. Any payer adapter maps to it. The LLM never sees the contract as a prompt unless it needs to fill a gap in note interpretation. Treat the contract as an interface; version it when payers add new benefit types.
Step 2: Stand up a payer FHIR integration
Most major US payers now expose a FHIR R4 CoverageEligibility operation. You post a CoverageEligibilityRequest and receive a CoverageEligibilityResponse. The request references a patient and coverage; the response nests insurance items with benefit arrays. Write a thin client that posts the bundle and raises on non-2xx.
import requests
def check_fhir_eligibility(payer_base: str, token: str, member_id: str, service_type: str) -> dict:
url = f"{payer_base}/CoverageEligibility"
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}
bundle = {
"resourceType": "CoverageEligibilityRequest",
"patient": {"reference": f"Patient/{member_id}"},
"coverage": [{"reference": "Coverage/123"}],
"item": [{"category": {"coding": [{"system": "http://terminology.hl7.org/CodeSystem/service-category", "code": service_type}]}}]
}
resp = requests.post(url, json=bundle, headers=headers, timeout=5)
resp.raise_for_status()
return resp.json()
Map the response to your contract with a pure function. FHIR places eligibility under insurance[0].item[0].benefit where type.coding[0].code equals eligible and allowedString is “yes” or “no”. Extract copay from a separate benefit code copay. Keep this mapping deterministic and unit-tested. Do not call an LLM here; the structure is regular enough for code.
Handling X12 fallbacks
If a payer only offers X12 270/271 via SFTP, wrap a converter that parses the 271 loop 2110C and emits the same contract. Your agent code stays identical; only the adapter changes.
Step 3: Use an LLM only for unstructured payer notes
Some payers return CoverageEligibilityResponse.insurance.item.note with free text like “Denied: prior auth required for CPT 99213”. Your agent should parse that into a structured reason. This is the only place an LLM earns its cost, because regex on payer notes breaks within a week of production.
import json
from openai import OpenAI
# Route through n4n.ai for automatic fallback across 240+ models from one OpenAI-compatible endpoint.
client = OpenAI(base_url="https://api.n4n.ai/v1", api_key="YOUR_KEY")
def parse_denial_note(note: str) -> dict:
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Extract denial reason and required action from payer note. Return JSON: {reason, action}"},
{"role": "user", "content": note}
],
response_format={"type": "json_object"}
)
return json.loads(resp.choices[0].message.content)
Force response_format to JSON. Cache the result keyed by note hash; payer notes repeat across members for the same policy rule. Set a short timeout (2s) on this call—if it fails, return the raw note and flag for human review.
Step 4: Build the agent loop with fallback and caching
Real-time means the agent must handle payer timeouts and LLM rate limits without blocking the request thread. Wrap each external call in a retry with exponential backoff. If the FHIR call fails after two tries, return a pending state rather than failing the whole flow.
import time, hashlib, json, requests
_cache = {}
def cached_parse(note: str):
key = hashlib.sha256(note.encode()).hexdigest()
if key in _cache:
return _cache[key]
result = parse_denial_note(note)
_cache[key] = result
return result
def agent_verify(payer_base, token, member_id, service_type):
try:
raw = check_fhir_eligibility(payer_base, token, member_id, service_type)
contract = normalize_fhir(raw)
except requests.Timeout:
return {"eligible": None, "status": "pending"}
if not contract["eligible"] and contract.get("note"):
try:
contract["denial_detail"] = cached_parse(contract["note"])
except Exception:
contract["denial_detail"] = {"raw": contract["note"]}
return contract
If you route through n4n.ai, the gateway forwards provider cache-control hints and meters per-token usage, so you can set cache_ttl on the request and get billed only for cache misses. That keeps the note-parsing tail cheap when a payer sends the same denial wording to thousands of patients.
Step 5: Expose a real-time HTTP endpoint
Wrap the agent in a FastAPI route. Accept a POST with member and service, return the contract. Use an async wrapper if you want to scale concurrent payer calls without threads.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class VerifyReq(BaseModel):
payer_base: str
token: str
member_id: str
service_type: str
@app.post("/verify")
def verify(req: VerifyReq):
return agent_verify(req.payer_base, req.token, req.member_id, req.service_type)
Deploy behind a load balancer with a 1-second upstream timeout. The agent’s internal 5-second payer timeout is stricter; tune to your payer SLA. Add a /health endpoint that checks the FHIR base reachability so orchestration can drain traffic during payer incidents.
Step 6: Verify success with integration tests
You cannot trust the agent without a golden test against a payer sandbox. Use pytest and responses to mock the FHIR endpoint and assert contract shape.
import responses, pytest
from your_module import check_fhir_eligibility, normalize_fhir
@responses.activate
def test_eligible_response():
responses.add(responses.POST, "https://payer-sandbox/fhir/CoverageEligibility",
json={"resourceType": "CoverageEligibilityResponse", "insurance": [{"item": [{"benefit": [{"type": {"coding": [{"code": "eligible"}]}, "allowedString": "yes"}, {"type": {"coding": [{"code": "copay"}]}, "allowedMoney": {"value": 20}}]}]}]},
status=200)
raw = check_fhir_eligibility("https://payer-sandbox/fhir", "tok", "m1", "office")
contract = normalize_fhir(raw)
assert contract["eligible"] is True
assert contract["copay"] == 20
Run the suite in CI. A green build means your AI agents insurance eligibility verification pipeline maps real payer data correctly. For end-to-end confirmation, point the test at a payer dev sandbox with a known test member and assert the expected copay and deductible. Load test with 50 concurrent verifications to confirm p99 latency stays under 800ms.
Operational notes
Keep the LLM out of the critical path. If the note parser fails, return the raw note and let a human review queue handle it. The deterministic FHIR mapping should drive 95% of verdicts; the LLM is a fallback for the long tail of payer-specific text.
Monitor token spend per request. With per-token metering you can alert when a single verification exceeds a threshold, usually a sign of a malformed note loop or a payer returning a novel essay-length denial.
AI agents insurance eligibility verification works best when the agent is boring: strict contracts, minimal LLM surface area, and clear fallback states. Ship the deterministic core first, then layer the model. When a payer changes their FHIR profile, you update one adapter, not the whole agent.