PPolyAI×Oracle Health (Cerner)
Scheduling · New-Patient Intake · Insurance · Prescriptions · Safety-Net

Voice scheduling on Cerner Millennium — intake, real-time insurance, and the clinical line drawn.

How PolyAI authenticates to Cerner Millennium, identifies or registers a patient, confirms or finds the physician, re-scopes its variant, validates and verifies insurance in real time for the appointment type, routes prescription refill requests to clinical pools, and books. Every node shows the request/response contract and, for each field, exactly where the data is sourced — captured from the caller, carried in conv.state from a prior call, set as a deployment attribute, or taken from the telephony ANI.

SMART on FHIR · R4 · Backend Services New-patient intake · EMPI · FHIR R4 Scheduling Coverage validated before write (pVerify seam) Variants re-scoped per provider Per-field data provenance Prescription refills · eligibility gate · clinical routing
Overview

Integration model

PolyAI runs as a backend client of Cerner Millennium's FHIR server. Reads go through FHIR R4 resources; scheduling writes go through standard FHIR R4 operations — Schedule + Slot search followed by POST Appointment. New-patient intake adds demographic duplicate-matching and FHIR R4 patient registration. Insurance is read from the FHIR Coverage resource, confirmed with the caller, validated against the payer before any write, and (when valid) written back provisionally — then a benefit check runs in real time, in-call against the payer via the pVerify eligibility seam.

The canonical booking sequence

Every booking is assembled from reusable building blocks in this order. Each block sources its inputs explicitly and writes named outputs to conv.state for the next block.

Reason & Safety-Net → ID Patient → Read Coverage → Confirm Physician (→ Find Physician) → Update Variants → Confirm Insurance (validate → provisional write) → Real-Time Benefit Check pVerify seam → Slot Negotiation → Schedule → Playback.

Coverage is read early so the payer is available to the in-network provider filter in Find Physician; the caller-facing confirm/update happens in its own step and validates the plan with the payer before writing it to Cerner; the live benefit check then runs after physician + variant so the rendering NPI and the service-type (appointment type) are both present.

Request path

No component requires a human logged into Cerner — correct for inbound telephony. The agent operates strictly in the administrative lane (see Scope & Compliance).

Caller (PSTN / SIP)
TelephonyTwilio · Five9 · Genesys · Amazon Connect · NICE
RuntimePolyAI dialog runtime
Flows · functions (conv.api) · variants · conv.state · deterministic readback · safety-net
Auth + transportSMART on FHIR · OAuth 2.0 Backend Services (Cerner) · pVerify OAuth2 (seam)
DataCerner Millennium (FHIR R4 + Schedule/Slot + Appointment) · pVerify 270/271 (seam)

Surface used by these flows

Resolved against Oracle_Health_Cerner_API_Documentation.md. "FHIR R4" = standard Cerner Millennium FHIR server; "FHIR R4 · Write" = write-scoped FHIR operations; "pVerify seam" = the real-time eligibility integration to be wired in the pVerify pass.

Resource / serviceOperationRole in flowsAvailability
Patient.Search (Demographics)GET (R4)Patient match / demographic duplicate checkFHIR R4
Patient.Search (Broadened)GET (R4)Fallback patient match — name + DOB onlyFHIR R4
Patient (create)POST (R4)Create provisional new-patient recordFHIR R4 · Write
Coverage.SearchGET (R4)Read insurance on file (payer + member ID)FHIR R4
EligibilitySummary (validation)POSTValidate plan is real/active before write (org NPI)pVerify seam
Coverage.CreatePOST (R4)Provisional patient-reported coverage writeFHIR R4 · Write
EligibilitySummary (benefit)POSTReal-time copay / PA check for the appointment typepVerify seam
Encounter.SearchGET (R4)Recent / historical providerFHIR R4
Practitioner.Read / SearchGET (R4)Resolve / find provider + NPIFHIR R4
Appointment.SearchGET (R4)Locate existing appointmentFHIR R4
Schedule.SearchGET (R4)Find provider schedule (step 1 of slot search)FHIR R4
Slot.SearchGET (R4)Find available time slots (step 2 of slot search)FHIR R4
Appointment (create)POST (R4)Book — new and established patientsFHIR R4 · Write
Appointment (cancel via PATCH)PATCH (R4)Cancel appointment (JSON Patch)FHIR R4 · Write
AppointmentResponsePOST (R4)Confirm / acknowledge appointmentFHIR R4 · Write
MedicationRequest.SearchGET (R4)Retrieve active medications + refill countFHIR R4
MedicationDispense.SearchGET (R4)Dispense history for remaining-refill calculationFHIR R4
RelatedPerson.SearchGET (R4)Authorized representative verificationFHIR R4
Encounter.Search (Eligibility)GET (R4)Completed-visit lookback (12-month refill gate)FHIR R4
Communication (create)POST (R4)Submit refill/renewal request to clinical poolFHIR R4 · Write
Transaction BundlePOST (R4)Atomic reschedule (book + cancel in one request)FHIR R4
i
Cerner uses the standard FHIR Schedule + Slot two-step pattern rather than Epic's custom $find/$book operations. A Slot tells you when something can happen, not whether it is clinically appropriate — appropriateness is handled by the configured appointment type, never by the agent.

Where the platform earns its place

  • Deterministic readback — confirmed values (DOB, slot, MRN, provider, copay) are templated from conv.state, not regenerated.
  • Stateful data lineage — every API field is sourced explicitly: caller capture, conv.state from a prior dip, a deployment/variant attribute, or the telephony ANI.
  • Validated, real-time insurance — the plan is checked with the payer before it's written to Cerner, and benefits are checked live for the appointment type; the agent reports the payer's result, it never adjudicates.
  • Variants re-scoped per providerconv.set_variant() re-points persona, department and appointment-type defaults the moment the provider is resolved.
  • Administrative-only by design — the agent schedules and verifies; it does not triage. The Safety-Net removes emergencies from the automated path.

App registration

ApplicationPolyAI
Prod Client IDa1b2c3d4-…-e5f6g7h8i9j0
SMART scope / FHIRSMART V1 · R4 · system/* read + Coverage CRUD + Scheduling
Grantclient_credentials + JWT assertion
Open SchedulingStandard FHIR R4 Patient + Appointment create
pVerify (seam)OAuth2 client_credentials; sub-account per NPI
Scope & Compliance

Why intake & insurance are in scope — and where the clinical line is

Written for both PolyAI deployment teams and prospective clients. It states PolyAI's operating position and the design rationale. It is not legal advice; final determination that a deployment is permissible in its jurisdiction rests with the customer's legal, compliance, and clinical leadership.

Operating principle

The agent performs administrative patient-access work. It does not assess symptoms, judge urgency, or decide what care a patient needs — and it never adjudicates coverage.

Identifying and registering patients, finding the right provider and appointment type, confirming insurance, and booking are administrative functions — the same work a front-desk coordinator performs. They become the practice of medicine or nursing only when someone evaluates symptoms to determine clinical urgency or the type of care required. That boundary is the design.

1 · The administrative / clinical line

The agent captures a reason-for-visit as an administrative label and maps it to a configured appointment type using the health system's own appointment-type configuration — the same administrative mapping configured in PowerChart for staff and patient self-scheduling. It does not interpret symptoms, rank severity, or select a specialty from a clinical presentation. "I need a cardiologist" or "my doctor referred me to dermatology" is administrative routing. "I have chest pain, what should I do?" is clinical — handled by the Safety-Net, not the agent.

2 · Schmitt-Thompson: referenced, not executed

Schmitt-Thompson protocols are the gold-standard telephone-triage decision support, used by the large majority of North American medical call centers — but they are physician-authored and administered by licensed nurses, peer-reviewed annually. PolyAI's decision is explicit: the agent does not run Schmitt-Thompson (or any) triage protocols and does not render dispositions. Where a caller introduces symptoms, the agent applies the Emergency Safety-Net and routes to the customer's licensed triage / clinical resource. Schmitt-Thompson stays with a licensed human.

3 · Why new-patient intake is permissible as an administrative function

New-patient registration and booking are administrative acts — automatable on the same basis as a human scheduler creating a chart and booking a visit — provided the interaction does not cross into symptom assessment or clinical triage. The flow collects identity and demographics, runs EMPI duplicate-matching, creates a provisional record (verified by staff at check-in), resolves an in-network provider, and books an administratively-mapped appointment type. The reason-for-visit is a routing label, not a clinical finding. That is what lets the function drive down patient leakage without becoming clinical decision-making.

The test PolyAI applies: if a step requires clinical judgement about the patient's condition, it does not belong in the automated path. Routing by stated preference, referral, or administrative reason-code is in scope; deciding what is wrong with the patient, or how urgent it is, is out.

4 · Insurance verification — report, never adjudicate

Confirming insurance, validating that a plan is real and active, and running a real-time benefit check are administrative (financial / registration) acts with no symptom content, so they sit fully inside scope. One bright-line rule governs them: the agent surfaces the payer's determination; it never makes one. It reads back "your plan is active and covers this visit, your copay is $40," or "the payer shows this plan as inactive — let me get you to billing." It does not deny coverage, decide medical necessity, or issue an adverse benefit determination. This keeps the function clear of AI adverse-determination restrictions — for example Texas S.B. 815 (effective Sept 2025), which prohibits utilization-review agents from using AI to make adverse determinations. Patient-reported coverage is validated with the payer before it is written, and even then is written provisional; the authoritative truth is set by the payer's 270/271 response and staff verification, not by the agent.

5 · The Emergency Safety-Net — the controlling guardrail

A conservative, customer-clinically-approved red-flag layer fronts every reason-for-visit. On a small set of unambiguous emergency cues — chest pain, difficulty breathing, stroke signs, severe bleeding, suicidal ideation — the agent stops the flow, instructs the caller to seek emergency care / dial 911, and warm-transfers to a designated clinical resource. It is tuned to over-escalate when uncertain. It is not triage: it assigns no disposition and no level of care — it removes the highest-risk interactions from the automated path entirely.

6 · Roles, ownership & sign-off

PolyAI provides the mechanics: the administrative flows, the reason→appointment-type mapping, the escalation routing, the data lineage, and the eligibility seam. The health-system customer owns the clinical content: the emergency red-flag set, the escalation targets (nurse line, 911 guidance, warm transfer), the appointment-type configuration, and clinical governance.

!
This document states PolyAI's operating position and design rationale. It is not legal advice. Each deployment should be reviewed and signed off by the customer's counsel and clinical leadership before go-live, and the administrative/clinical boundary recorded in the SOW.
Interactive Call Flows

Branch logic, fallbacks, and the API at every node

Established-patient flows. Pick one; decisions fan out into labelled branches with their own terminals — loop-backs, CC handoffs, the continuing path. Click any node for utterances, request/response contracts with per-field data provenance, fallbacks, and capture strategy. New-patient intake has its own section. Click a building block to drill in.

Use Case · Leakage Reduction

Booking a brand-new patient — intake, insurance, no triage

The high-value, higher-care flow. A caller with no record wants to be seen. The agent runs the Safety-Net, matches against EMPI to avoid a duplicate, registers a provisional record only on no-match, takes insurance up front (so the provider search can filter in-network), validates the plan with the payer before writing it, finds a provider accepting new patients, re-scopes its variant, verifies benefits live, and books a new-patient visit type. The weight-bearing decisions — EMPI disposition, the Safety-Net, coverage validation, and the eligibility result — are explicit branches.

Why this differs from scheduling an existing patient

ConcernEstablished patientNew patient
RecordExists — resolve FHIR Patient IDMay not exist — EMPI match, then create only on no-match
Duplicate riskLowHigh — ambiguous match → data-stewardship handoff, never auto-create/merge
InsuranceOn file → confirm, validate, update if changedNone on file → capture up front + validate before write (needed for in-network search)
ProviderConfirm the one they've seenNo history — directory search (specialty + location + accepting-new + network)
Appointment typeEstablished appointment typesNew-patient appointment types (longer; different codes)
Booking writeFHIR R4 POST AppointmentFHIR R4 POST Appointment (same endpoint)
Three states, not two. "New to this provider but already in the system" is still an established record (skip create). "New to the health system" is the registration path. The EMPI match node tells them apart deterministically, before any write.
Voice Data-Capture Playbook

Handling the hard-to-capture data points

Voice ASR makes some fields risky. These are the patterns PolyAI uses — validate against known values first, fall back to spelling only when needed, and treat reason-for-visit as an administrative label, never a clinical assessment.

API Reference

Every data dip — request, response & data source

Consolidated contract for all endpoints across the flows. For every field, the source line states exactly where the value comes from: caller capture, conv.state from a prior API call, a deployment / variant attribute, or the telephony ANI. Response fields show where the value is stored and what consumes it downstream.

Authentication

SMART on FHIR — Backend Services

OAuth 2.0 client_credentials with a signed JWT. No interactive login — correct for an inbound call where no clinician or patient can complete a browser consent. The same token serves read FHIR, scheduling operations (Schedule, Slot, Appointment), and Coverage writes. The pVerify eligibility seam uses its own OAuth2 token (covered in the pVerify pass).

Token flow

Why backend services — not patient or clinician OAuth

OAuth flowDesigned forVoice-suitable?Why
Backend Services UseServer-to-server, no user presentYesJWT signed with a private key; Agent Studio auto-refreshes the token.
Clinician / EHR launch NoApps launched inside PowerChartNoNeeds a logged-in Cerner user — impossible on an inbound call.
Patient (HealtheLife) NoPatient portal appsNoNeeds HealtheLife credentials + browser consent. (Some FHIR APIs also behave differently in a patient-facing context.)
Implementation

Wiring Cerner Millennium into PolyAI Agent Studio

Build with Poly Agent Builder (non-technical) or the ADK (developers) — same dialog-native runtime. The data-lineage discipline below is what makes the deployment auditable.

From integration request to live agent

1

Enable Cerner Millennium FHIR

Configure > Integrations > Cerner. Register the application via the Cerner Code Console; production access requires Oracle PartnerNetwork (OPN) validation. Enable system-level scopes for Patient, Coverage, Appointment, Schedule, and Slot. (pVerify provisioned separately in the pVerify pass.)

2

Define API operations

Configure > APIs: declare each endpoint with environment-specific base URLs and OAuth 2.0. All scheduling operations use standard FHIR R4 — no private vendor-gated endpoints. Configure Coverage write scope (cruds).

3

Write data-dip functions with explicit lineage

Each function reads inputs from a declared source — caller capture, a conv.state value from a prior dip, a variant attribute, or ANI — calls conv.api.cerner.<op>(...), and writes named results back to conv.state.

4

Re-scope the variant on provider resolution

The moment Confirm/Find Physician resolves a provider, call conv.set_variant(specialty) and write provider / department / appointment-type to conv.state so every downstream {{attr:…}}, slot search and write re-points.

5

Wire the Safety-Net + eligibility seam

Front every reason-for-visit with the customer-approved red-flag set; map reason→appointment type via the configured type mapping; validate coverage with the payer before the Cerner write; run the live benefit check after physician + variant. Keep clinical content as customer-owned configuration.

Python New Patient Intake — demographic match, then create-on-no-match (with lineage)

new_patient_intake.py def new_patient_intake(conv, flow): # caller-captured this turn: name, dob; phone seeded from ANI r = conv.api.cerner.patient_search(params={ "Name": conv.state.name_for_lookup, # src: caller capture "DateOfBirth": conv.state.dob, # src: caller capture "Address": {"PhoneNumbers": [conv.state.phone]}}) # src: ANI cands = r.json().get("entry", []) if r.status_code==200 else [] if not cands: # fallback: R4 $match cands = _match_fallback(conv) grade = empi_confidence(cands, conv) # strong / none / ambiguous if grade == "strong": conv.state.patient_id = _patient_id(cands[0]) # → conv.state (stateful) return flow.goto_step("Verify Identity") if grade == "ambiguous": return conv.functions.handoff("empi_stewardship") # never auto-create/merge reg = conv.api.cerner.patient_create(json=_demographics(conv)) # provisional conv.state.patient_id = reg.json()["id"] # → conv.state (staff-verified at check-in) flow.goto_step("Confirm Insurance")

pVerify seam Validate Coverage — check the plan is real/active before the Cerner write (org NPI)

validate_coverage.py def validate_coverage(conv, flow): # runs BEFORE Coverage.Create. Uses the org/group NPI from config, # so it works before the rendering provider is resolved. if not conv.config.pverify_live: # seam not wired yet \u2192 degrade gracefully conv.state.coverage_unverified = True # write provisional + flag front desk return _write_provisional(conv, flow) res = conv.api.pverify.eligibility_summary(json={ "payerCode": conv.state.payer, # ← caller capture (this step) "memberID": conv.state.member_id, # ← caller capture (this step) "provider": {"npi": conv.config.org_npi}, # ← CONFIG group/org NPI "patientDOB": conv.state.dob}) # ← ID Patient summary = res.json().get("PlanCoverageSummary", {}) aaa = res.json().get("AAAErrors", []) if summary.get("Status") == "Active": _write_provisional(conv, flow) # NOW the Cerner write is safe flow.goto_step("Next") elif aaa: # subscriber not found / invalid ID / DOB mismatch if conv.state.validate_tries < 2: conv.state.validate_tries += 1 return flow.goto_step("Re-capture Member ID") # loop, no write conv.functions.handoff("billing") # exhausted \u2192 handoff, no write else: # Inactive / termed conv.say(_report_payer_result(res)); conv.functions.handoff("billing") # no write
Validation seam: answers only \u201cis this a real, active plan for this member?\u201d using the org NPI \u2014 no rendering provider or service type needed, so it runs inside Confirm Insurance ahead of the write. Distinct from the downstream benefit check. When the seam isn't live, it degrades to write provisional + flag rather than blocking the booking.

pVerify seam Real-Time Benefit Check — live 270/271 copay / PA for the appointment type (with lineage)

realtime_benefit.py def realtime_benefit(conv, flow): # every input is a stateful variable sourced from a prior step: res = conv.api.pverify.eligibility_summary(json={ "payerCode": conv.state.payer, # ← Confirm Insurance (validated) "memberID": conv.state.member_id, # ← Confirm Insurance (validated) "provider": {"npi": conv.state.rendering_npi}, # ← Confirm/Find Physician "serviceType": conv.state.service_type, # ← appointment_type (type mapping) "patientDOB": conv.state.dob}) # ← ID Patient s = res.json().get("PlanCoverageSummary", {}).get("Status") if s == "Active" and _covered(res, conv.state.service_type): conv.state.copay = _service_copay(res, conv.state.service_type) flow.goto_step("Slot Negotiation") # agent STATES copay, does not adjudicate elif s in ("Inactive", "NotFound"): conv.say(_report_payer_result(res)); conv.functions.handoff("billing") elif _needs_auth(res): # PA / referral / not covered for service conv.say(_report_payer_result(res)); conv.functions.handoff("referral_billing") else: # async / non-EDI payer (~1%) conv.state.coverage_unverified = True # book + flag for front desk flow.goto_step("Slot Negotiation")
Seam: the conv.api.pverify.* call is the integration to be wired in the pVerify pass. The Cerner side (Coverage read + provisional write) is in-scope now; this node is drawn so the architecture is complete and the hook is unmistakable. It needs the rendering NPI + service type, which is why it runs after physician + variant \u2014 unlike the validation check.
Prescription Refills & Renewals

Refill requests, eligibility checks, and clinical pool routing

The agent checks the patient's active medications, computes remaining refills, verifies visit eligibility (12-month lookback), and routes the request to the appropriate clinical pool. Controlled substances require an in-person visit. The agent never dispenses, adjudicates, or overrides — it routes.

Prescription refill logic

ScenarioAgent actionAPI surface
Refills remaining on fileDirect caller to their pharmacy — no request submittedMedicationRequest + MedicationDispense (read)
No refills remaining — visit within 12 monthsSubmit refill request to clinical poolCommunication (POST) to clinical pool
No refills remaining — no visit in 12 monthsSchedule a visit and submit refill requestSchedule + Slot + POST Appointment + Communication
Controlled substanceSchedule in-person visit; submit request with HIGH priorityPOST Appointment + Communication (priority: urgent)
Clinical question about medicationTransfer to Access Center staffNo API — CC handoff
!
Authorized representative gate: because refill requests involve sharing medication details (PHI), the agent verifies the caller is an authorized representative via RelatedPerson when the caller is not the patient. Scheduling actions do not require this check.

Remaining-refill calculation

refill_calc.py def remaining_refills(conv, medication_request_id): # Step 1: get authorized refill count from the prescription rx = conv.api.cerner.medication_request_read(medication_request_id) authorized = rx["dispenseRequest"].get("numberOfRepeatsAllowed", 0) # Step 2: count completed dispenses for this prescription dispenses = conv.api.cerner.medication_dispense_search(params={ "patient": conv.state.patient_id, "prescription": f"MedicationRequest/{medication_request_id}", "status": "completed"}) filled = len(dispenses.json().get("entry", [])) # numberOfRepeatsAllowed is refills BEYOND initial fill # total authorized fills = numberOfRepeatsAllowed + 1 remaining = max(0, authorized - filled) conv.state.refills_remaining = remaining return remaining

Refill request message content

The Communication payload carries everything the clinical team needs to act without calling back:

FieldSource
Patient nameconv.state.patient_name ← ID Patient
MRNconv.state.mrn ← Patient.identifier
Medication + dosageconv.state.medication ← MedicationRequest
PharmacyCaller capture (confirmed)
Eligibility statusconv.state.has_recent_visit ← Encounter eligibility check
PriorityHIGH for controlled substances, NORMAL otherwise
Additional Considerations

Choices we made — and the ones we deliberately did not

The reasoning behind each choice, so it holds up to clinical and technical review.

Roads not taken

API endpoints & interfaces we deliberately did not choose

Surfaces that exist in the Cerner Millennium ecosystem and could plausibly have been used here, with the reason each was set aside for this conversational, real-time, administrative use case. Confirm specifics against Oracle_Health_Cerner_API_Documentation.md for a given instance.