By Sagar Shankaran, Founder of CallSphere
Build an AI agent that detects when patients need medication refills, routes approval requests to providers, coordinates with pharmacies via NCPDP SCRIPT, and tracks prescription fulfillment end to end.
Key takeaways
Prescription refill requests account for a significant portion of inbound calls to medical practices. Each request triggers a multi-step workflow: verify the prescription, check remaining refills, get provider approval, and notify the pharmacy. When done manually, refill requests take 5 to 10 minutes each and are prone to communication delays.
An AI refill management agent handles this entire chain — from detecting that a patient needs a refill to confirming that the pharmacy has processed it.
Start by modeling the prescription lifecycle, including refill counts, authorization status, and pharmacy details.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for healthcare in your browser — 60 seconds, no signup.
sequenceDiagram
autonumber
participant Caller as Caller
participant Agent as CallSphere Agent
participant API as CRM API
participant DB as CRM Database
participant Webhook as Webhook Listener
Caller->>Agent: Inbound call begins
Agent->>Agent: STT plus intent detection
Agent->>API: Lookup contact by phone
API->>DB: Read contact record
DB-->>API: Contact and history
API-->>Agent: Personalized context
Agent->>API: Create call activity
Agent->>API: Update deal stage
API->>Webhook: Outbound webhook fires
Webhook-->>Agent: Confirmed
Agent->>Caller: Spoken confirmation
from dataclasses import dataclass, field
from datetime import date, datetime, timedelta
from typing import Optional
from enum import Enum
import uuid
class PrescriptionStatus(Enum):
ACTIVE = "active"
EXPIRED = "expired"
DISCONTINUED = "discontinued"
ON_HOLD = "on_hold"
class RefillRequestStatus(Enum):
PENDING = "pending"
APPROVED = "approved"
DENIED = "denied"
SENT_TO_PHARMACY = "sent_to_pharmacy"
FILLED = "filled"
PICKED_UP = "picked_up"
@dataclass
class Prescription:
id: str
patient_id: str
provider_id: str
medication_name: str
dosage: str
frequency: str
quantity: int
refills_authorized: int
refills_remaining: int
prescribed_date: date
expiration_date: date
pharmacy_id: str
status: PrescriptionStatus = PrescriptionStatus.ACTIVE
last_filled: Optional[date] = None
days_supply: int = 30
@dataclass
class RefillRequest:
id: str = field(
default_factory=lambda: str(uuid.uuid4())
)
prescription_id: str = ""
patient_id: str = ""
requested_at: datetime = field(
default_factory=datetime.utcnow
)
status: RefillRequestStatus = RefillRequestStatus.PENDING
provider_approved: bool = False
approved_by: Optional[str] = None
approved_at: Optional[datetime] = None
pharmacy_confirmation: Optional[str] = None
notes: str = ""
The agent proactively identifies patients who are running low on medication based on their fill history and days supply. This enables outreach before the patient runs out.
class RefillDetector:
def __init__(self, db):
self.db = db
async def find_patients_needing_refills(
self, lookahead_days: int = 7,
) -> list[dict]:
cutoff = date.today() + timedelta(days=lookahead_days)
rows = await self.db.fetch("""
SELECT
rx.id AS prescription_id,
rx.patient_id,
p.first_name || ' ' || p.last_name AS name,
p.phone, p.email,
rx.medication_name, rx.dosage,
rx.refills_remaining,
rx.last_filled,
rx.days_supply,
(rx.last_filled + rx.days_supply) AS runs_out,
rx.pharmacy_id
FROM prescriptions rx
JOIN patients p ON p.id = rx.patient_id
WHERE rx.status = 'active'
AND rx.refills_remaining > 0
AND (rx.last_filled + rx.days_supply) <= $1
AND NOT EXISTS (
SELECT 1 FROM refill_requests rr
WHERE rr.prescription_id = rx.id
AND rr.status IN (
'pending', 'approved',
'sent_to_pharmacy'
)
)
ORDER BY runs_out ASC
""", cutoff)
return [dict(r) for r in rows]
async def validate_refill_eligibility(
self, prescription_id: str,
) -> dict:
rx = await self.db.fetchrow("""
SELECT * FROM prescriptions
WHERE id = $1
""", prescription_id)
if not rx:
return {"eligible": False, "reason": "not_found"}
if rx["status"] != "active":
return {
"eligible": False,
"reason": f"prescription_{rx['status']}",
}
if rx["refills_remaining"] <= 0:
return {"eligible": False, "reason": "no_refills"}
if rx["expiration_date"] < date.today():
return {"eligible": False, "reason": "expired"}
return {
"eligible": True,
"refills_remaining": rx["refills_remaining"],
"medication": rx["medication_name"],
"dosage": rx["dosage"],
}
Certain refills require explicit provider approval — especially controlled substances or when the prescription needs modification. The agent routes these requests through a structured approval queue.
class ProviderApprovalQueue:
def __init__(self, db, notification_service):
self.db = db
self.notify = notification_service
async def submit_for_approval(
self, refill_request: RefillRequest,
prescription: Prescription,
requires_review: bool = False,
) -> RefillRequest:
auto_approve = (
not requires_review
and prescription.refills_remaining > 0
and prescription.expiration_date > date.today()
)
if auto_approve:
refill_request.status = RefillRequestStatus.APPROVED
refill_request.provider_approved = True
refill_request.approved_by = "auto"
refill_request.approved_at = datetime.utcnow()
else:
refill_request.status = RefillRequestStatus.PENDING
await self.notify.send_to_provider(
provider_id=prescription.provider_id,
message=(
f"Refill request for "
f"{prescription.medication_name} "
f"{prescription.dosage} - "
f"Patient {refill_request.patient_id}. "
f"Refills remaining: "
f"{prescription.refills_remaining}"
),
action_url=(
f"/refills/{refill_request.id}/review"
),
)
await self.db.execute("""
INSERT INTO refill_requests
(id, prescription_id, patient_id, status,
provider_approved, approved_by, approved_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
""", refill_request.id, refill_request.prescription_id,
refill_request.patient_id,
refill_request.status.value,
refill_request.provider_approved,
refill_request.approved_by,
refill_request.approved_at)
return refill_request
async def process_provider_decision(
self, request_id: str, approved: bool,
provider_id: str, notes: str = "",
):
status = (
RefillRequestStatus.APPROVED if approved
else RefillRequestStatus.DENIED
)
await self.db.execute("""
UPDATE refill_requests
SET status = $2, provider_approved = $3,
approved_by = $4, approved_at = $5, notes = $6
WHERE id = $1
""", request_id, status.value, approved,
provider_id, datetime.utcnow(), notes)
Once approved, the agent sends the refill authorization to the pharmacy using the NCPDP SCRIPT standard, the electronic prescribing protocol used across US pharmacies.
class PharmacyNotifier:
def __init__(self, escript_client):
self.client = escript_client
async def send_refill_authorization(
self, prescription: Prescription,
refill_request: RefillRequest,
db,
) -> str:
message = {
"message_type": "REFRES", # refill response
"pharmacy_ncpdp": prescription.pharmacy_id,
"prescriber_npi": await self._get_npi(
prescription.provider_id, db
),
"medication": {
"drug_description": (
prescription.medication_name
),
"strength": prescription.dosage,
"quantity": prescription.quantity,
"days_supply": prescription.days_supply,
"refills_authorized": 1,
},
"patient_id": prescription.patient_id,
}
confirmation = await self.client.send(message)
await db.execute("""
UPDATE refill_requests
SET status = 'sent_to_pharmacy',
pharmacy_confirmation = $2
WHERE id = $1
""", refill_request.id, confirmation["tracking_id"])
await db.execute("""
UPDATE prescriptions
SET refills_remaining = refills_remaining - 1,
last_filled = CURRENT_DATE
WHERE id = $1
""", prescription.id)
return confirmation["tracking_id"]
async def _get_npi(self, provider_id, db):
row = await db.fetchrow(
"SELECT npi FROM providers WHERE id = $1",
provider_id,
)
return row["npi"]
The agent monitors the complete refill lifecycle and notifies the patient at each stage.
Still reading? Stop comparing — try CallSphere live.
See the healthcare AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
class RefillTracker:
def __init__(self, db, sms_client):
self.db = db
self.sms = sms_client
async def check_and_notify(self, request_id: str):
req = await self.db.fetchrow("""
SELECT rr.*, p.phone, p.first_name,
rx.medication_name
FROM refill_requests rr
JOIN patients p ON p.id = rr.patient_id
JOIN prescriptions rx
ON rx.id = rr.prescription_id
WHERE rr.id = $1
""", request_id)
status = req["status"]
messages = {
"approved": (
f"Hi {req['first_name']}, your refill for "
f"{req['medication_name']} has been approved "
f"and sent to your pharmacy."
),
"denied": (
f"Hi {req['first_name']}, your provider "
f"needs to discuss your "
f"{req['medication_name']} refill with you. "
f"Please call the office."
),
"filled": (
f"Hi {req['first_name']}, your "
f"{req['medication_name']} is ready for "
f"pickup at your pharmacy."
),
}
if status in messages:
await self.sms.send(req["phone"], messages[status])
Controlled substances (Schedule II-V) always require explicit provider review — the auto-approval path is disabled. The agent flags these requests with the DEA schedule classification and presents additional verification fields to the provider, including the patient's prescription drug monitoring program (PDMP) report. Schedule II medications cannot be refilled at all and require a new prescription.
The agent informs the patient that no refills are available and offers to send a new prescription request to their provider. It creates a "renewal request" instead of a refill request, which goes through the full provider review workflow. The provider can then issue a new prescription with a fresh refill count if clinically appropriate.
The agent maintains the patient's current preferred pharmacy and allows pharmacy changes at refill time. When a pharmacy change is detected, the agent sends a cancellation to the old pharmacy and routes the new fill to the updated pharmacy, ensuring no duplicate dispensing occurs.
#PrescriptionRefill #PharmacyIntegration #HealthcareAI #NCPDPSCRIPT #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
AWS HealthScribe became the open scribe layer EHR vendors built on top of in 2026. Here's the API surface, the per-encounter pricing, the BAA terms.
Apollo, Manipal, and Narayana scaled AI agents across Bangalore in 2026. Here's the deployments across radiology, intake, and follow-up, the costs.
Notable's AI agents now handle scheduling, intake, and revenue cycle for 6,000+ clinics in 2026. Here's the multi-agent architecture, the per-clinic pricing.
Abridge raised $250M in April 2026 at a $2.7B valuation. We break down the deployment numbers, the EHR integrations across Epic and Cerner. The Q2 2026 buyer briefing.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI