By Sagar Shankaran, Founder of CallSphere
Build a conversational booking agent that integrates with calendar APIs, handles timezone conversions, checks real-time availability, and manages confirmation and reminder flows.
Key takeaways
Appointment scheduling is one of the highest-ROI applications of conversational AI. Every business that relies on meetings — from healthcare clinics to SaaS sales teams — loses revenue when prospects drop off during the booking process. A conversational booking agent eliminates that friction by handling the entire flow through natural language: understanding the request, checking availability, proposing times, handling timezone differences, and sending confirmations.
A booking agent needs four capabilities: natural language understanding to parse scheduling intent, a calendar integration layer for real-time availability, timezone logic to prevent mismatches, and a notification system for confirmations and reminders. We will wire these together using tool-calling with the OpenAI Agents SDK.
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
The foundation is a clean abstraction over your calendar provider. This example uses Google Calendar, but the pattern applies to any provider with a REST API.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
from datetime import datetime, timedelta
from dataclasses import dataclass
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
@dataclass
class TimeSlot:
start: datetime
end: datetime
available: bool = True
class CalendarService:
def __init__(self, credentials: Credentials):
self.service = build("calendar", "v3", credentials=credentials)
def get_available_slots(
self, calendar_id: str, date: str, duration_minutes: int = 30
) -> list[TimeSlot]:
"""Fetch free slots for a given date."""
day_start = datetime.fromisoformat(f"{date}T09:00:00")
day_end = datetime.fromisoformat(f"{date}T17:00:00")
body = {
"timeMin": day_start.isoformat() + "Z",
"timeMax": day_end.isoformat() + "Z",
"items": [{"id": calendar_id}],
}
result = self.service.freebusy().query(body=body).execute()
busy_periods = result["calendars"][calendar_id]["busy"]
# Build slots and mark busy ones
slots = []
current = day_start
while current + timedelta(minutes=duration_minutes) <= day_end:
slot_end = current + timedelta(minutes=duration_minutes)
is_busy = any(
datetime.fromisoformat(b["start"].replace("Z", ""))
< slot_end
and datetime.fromisoformat(b["end"].replace("Z", ""))
> current
for b in busy_periods
)
slots.append(TimeSlot(
start=current, end=slot_end, available=not is_busy
))
current = slot_end
return [s for s in slots if s.available]
def create_event(
self, calendar_id: str, slot: TimeSlot, attendee_email: str,
summary: str,
) -> str:
event = {
"summary": summary,
"start": {"dateTime": slot.start.isoformat(), "timeZone": "UTC"},
"end": {"dateTime": slot.end.isoformat(), "timeZone": "UTC"},
"attendees": [{"email": attendee_email}],
}
result = self.service.events().insert(
calendarId=calendar_id, body=event, sendUpdates="all"
).execute()
return result["htmlLink"]
Timezone errors are the single most common failure mode in booking agents. Always store times in UTC internally and convert to the user's timezone only at the presentation layer.
from zoneinfo import ZoneInfo
def convert_slots_to_local(
slots: list[TimeSlot], user_timezone: str
) -> list[dict]:
tz = ZoneInfo(user_timezone)
return [
{
"start": slot.start.replace(tzinfo=ZoneInfo("UTC"))
.astimezone(tz)
.strftime("%I:%M %p"),
"end": slot.end.replace(tzinfo=ZoneInfo("UTC"))
.astimezone(tz)
.strftime("%I:%M %p"),
"start_utc": slot.start.isoformat(),
}
for slot in slots
]
def detect_timezone_from_message(message: str) -> str | None:
"""Simple keyword detection for timezone hints."""
tz_map = {
"est": "America/New_York",
"eastern": "America/New_York",
"cst": "America/Chicago",
"central": "America/Chicago",
"pst": "America/Los_Angeles",
"pacific": "America/Los_Angeles",
"ist": "Asia/Kolkata",
"gmt": "Europe/London",
"utc": "UTC",
}
lower = message.lower()
for keyword, tz in tz_map.items():
if keyword in lower:
return tz
return None
The booking agent uses tools to check availability, propose times, and create events. The LLM handles the conversational flow while the tools handle the data operations.
from agents import Agent, Runner, function_tool
@function_tool
def check_availability(date: str, duration_minutes: int = 30) -> str:
"""Check available time slots for a given date (YYYY-MM-DD)."""
cal = CalendarService(get_credentials())
slots = cal.get_available_slots("primary", date, duration_minutes)
if not slots:
return f"No available slots on {date}."
local_slots = convert_slots_to_local(slots, "America/New_York")
lines = [f"- {s['start']} to {s['end']}" for s in local_slots[:6]]
return f"Available slots on {date}:\n" + "\n".join(lines)
@function_tool
def book_appointment(
date: str, time_utc: str, attendee_email: str, purpose: str
) -> str:
"""Book an appointment at the specified UTC time."""
cal = CalendarService(get_credentials())
start = datetime.fromisoformat(time_utc)
slot = TimeSlot(start=start, end=start + timedelta(minutes=30))
link = cal.create_event("primary", slot, attendee_email, purpose)
return f"Appointment booked. Calendar link: {link}"
booking_agent = Agent(
name="BookingAgent",
instructions="""You are a scheduling assistant. Help users book
appointments by checking availability and confirming bookings.
Always confirm the date, time, and timezone before booking.
Ask for the attendee's email if not provided.""",
tools=[check_availability, book_appointment],
)
After booking, the agent should send a confirmation message immediately and schedule reminders. A simple task queue handles the deferred sends.
import asyncio
from datetime import datetime, timedelta
async def send_confirmation(email: str, details: dict, notifier):
message = (
f"Your appointment is confirmed for "
f"{details['date']} at {details['time']}.\n"
f"Purpose: {details['purpose']}\n"
f"Calendar link: {details['link']}"
)
await notifier.send_email(email, "Appointment Confirmed", message)
async def schedule_reminder(
email: str, appointment_time: datetime, notifier
):
reminder_time = appointment_time - timedelta(hours=1)
delay = (reminder_time - datetime.utcnow()).total_seconds()
if delay > 0:
await asyncio.sleep(delay)
await notifier.send_email(
email,
"Reminder: Appointment in 1 hour",
f"Your appointment is in 1 hour at "
f"{appointment_time.strftime('%I:%M %p UTC')}.",
)
Query the freebusy endpoint for all team members simultaneously and compute the intersection of available slots. Present only times where at least one qualified team member is free, and assign the meeting to whichever available member best matches the prospect's needs.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Use a date parsing library like dateparser or python-dateutil to resolve relative dates. Always confirm the resolved date with the user before checking availability. For example, respond with "I understand you mean Tuesday, March 24th — is that correct?" before proceeding.
Use optimistic locking. Before creating the event, re-check availability one final time. If the slot was taken between the user's selection and the booking attempt, inform them immediately and offer the next available slot. Google Calendar's API will also reject conflicting events if configured with the sendUpdates parameter.
#SchedulingAgent #CalendarIntegration #ConversationalAI #TimezoneHandling #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.
A founder's guide to page chat: web page chat box options, best live chat for small business, and how CallSphere ships an embed in 5 minutes.
A founder's guide to building a chatbot for answering questions on your website: RAG, voice, and how CallSphere ships one in 3-5 days.
Good messaging apps in 2026 ranked by a founder running 6 AI voice agents. Signal, iMessage, WhatsApp, Telegram, and where AI fits.
Group chat apps in 2026 ranked by a founder running a 14-tool AI platform. Slack, Discord, Teams, Telegram, and where AI voice chat fits.
Create a chat bot in 2026 means LLM-backed agents, not decision trees. Here is the working guide: platforms, build steps, and what actually matters.
Best chat software in 2026: a founder running 6 AI agents ranks website chat tools, live chat, and AI chat platforms. Real prices, real picks.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.