Understanding AI Appointment Booking
AI appointment booking is an intelligent scheduling system that uses natural language processing and machine learning to handle appointment requests through voice calls, SMS, chat, or web interfaces ā without human intervention. For clinics and salons, this means a virtual receptionist that works 24/7, understands customer requests in natural language, checks availability in real time, and books appointments instantly.
The core technology stack typically includes:
- Speech-to-text / NLP engine ā Deepgram, Whisper, or Twilio Voice for understanding spoken requests
- LLM orchestration layer ā OpenAI, Anthropic, or open-source models for intent classification and conversation flow
- Calendar API integration ā Google Calendar, Calendly, Acuity, or Square Appointments
- CRM bridge ā Syncing bookings to clinic management software like Jane App, Mindbody, or Fresha
- Communication gateway ā Twilio for voice/SMS, SendGrid for email confirmations
Why This Matters for Clinics and Salons
Small health and beauty businesses lose an estimated 30-40% of new customer calls to voicemail. A typical clinic front desk spends 15-25 hours per week just on phone scheduling. AI booking agents solve three critical pain points at once:
- Zero missed calls ā Every after-hours call gets answered and converted into a booked appointment
- Reduced no-shows ā Automated SMS and email reminders cut no-show rates by 40-60%
- Staff reallocation ā Front desk teams focus on in-person patient experience instead of phone tag
For developers selling this solution, the value proposition is razor-sharp: a clinic paying $18/hour for front desk labor spends roughly $3,000/month. An AI booking agent at $300-600/month replaces 60-80% of that scheduling workload ā an immediate 5-10x ROI for the business owner.
Architecture Overview
Here is the high-level flow for an AI booking agent handling an inbound phone call:
Customer Dials Clinic Number
ā
ā¼
Twilio Voice Webhook
ā
ā¼
Speech-to-Text (Deepgram)
ā
ā¼
LLM Intent Classifier + Slot Filling
ā
ā¼
Calendar Availability Check (Google Cal API)
ā
ā¼
Book Appointment + Send Confirmation
ā
ā¼
Text-to-Speech Response to Caller
Building the Core Booking Engine
Step 1: Setting Up the LLM-Powered Intent Parser
The heart of the system is a function that takes raw user input (transcribed speech or chat text) and extracts structured booking intent. Here's a production-ready example using OpenAI function calling:
import openai
import json
from datetime import datetime, timedelta
openai.api_key = "sk-your-key-here"
def extract_booking_intent(user_message: str, conversation_history: list = None):
"""
Extract structured appointment booking intent from natural language input.
Returns a dict with service_type, preferred_date, preferred_time, customer_name.
"""
system_prompt = """You are an appointment scheduling assistant for a multi-service clinic.
Extract the following from the user's message:
- service_type: The specific service requested (e.g., "haircut", "dentist cleaning", "massage")
- preferred_date: Date in YYYY-MM-DD format. If relative like "next Tuesday", compute the actual date.
- preferred_time: Time in HH:MM 24-hour format. If vague like "morning", default to 09:00.
- customer_name: Full name of the person booking.
- customer_phone: Phone number if provided.
If any field is missing, set it to null. Today's date is {today}.
Return ONLY valid JSON with no additional text.""".format(
today=datetime.now().strftime("%Y-%m-%d")
)
messages = [{"role": "system", "content": system_prompt}]
if conversation_history:
messages.extend(conversation_history)
messages.append({"role": "user", "content": user_message})
response = openai.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={"type": "json_object"},
temperature=0.1,
max_tokens=300
)
intent_data = json.loads(response.choices[0].message.content)
return intent_data
# Example usage
user_input = "Hi, I'm Sarah Johnson. I need a teeth cleaning sometime next Wednesday morning."
result = extract_booking_intent(user_input)
print(json.dumps(result, indent=2))
# Output:
# {
# "service_type": "teeth cleaning",
# "preferred_date": "2025-04-16",
# "preferred_time": "09:00",
# "customer_name": "Sarah Johnson",
# "customer_phone": null
# }
Step 2: Calendar Availability Checker
Once you have the structured intent, query the business calendar to find open slots. This example uses Google Calendar API:
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
from datetime import datetime, timedelta
class CalendarManager:
def __init__(self, service_account_file: str, calendar_id: str):
self.credentials = Credentials.from_service_account_file(
service_account_file,
scopes=['https://www.googleapis.com/auth/calendar']
)
self.service = build('calendar', 'v3', credentials=self.credentials)
self.calendar_id = calendar_id
def find_available_slots(self, date_str: str, service_duration_minutes: int = 60):
"""
Return available time slots for a given date.
Business hours: 09:00-17:00, Mon-Sat.
"""
date_obj = datetime.strptime(date_str, "%Y-%m-%d")
# Define business hours
time_min = datetime(date_obj.year, date_obj.month, date_obj.day, 9, 0, 0)
time_max = datetime(date_obj.year, date_obj.month, date_obj.day, 17, 0, 0)
events_result = self.service.events().list(
calendarId=self.calendar_id,
timeMin=time_min.isoformat() + 'Z',
timeMax=time_max.isoformat() + 'Z',
singleEvents=True,
orderBy='startTime'
).execute()
booked_slots = []
for event in events_result.get('items', []):
start = event['start'].get('dateTime', event['start'].get('date'))
end = event['end'].get('dateTime', event['end'].get('date'))
booked_slots.append({
'start': start,
'end': end,
'summary': event.get('summary', 'Busy')
})
# Generate all possible 30-min slot increments
all_slots = []
current = time_min
while current < time_max:
slot_end = current + timedelta(minutes=service_duration_minutes)
if slot_end <= time_max:
is_available = True
for booked in booked_slots:
booked_start = datetime.fromisoformat(booked['start'].replace('Z', '+00:00'))
booked_end = datetime.fromisoformat(booked['end'].replace('Z', '+00:00'))
if current < booked_end and slot_end > booked_start:
is_available = False
break
if is_available:
all_slots.append(current.strftime("%H:%M"))
current += timedelta(minutes=30)
return all_slots[:8] # Return first 8 available slots
# Usage
cal = CalendarManager("service-account.json", "clinic-calendar-id@group.calendar.google.com")
slots = cal.find_available_slots("2025-04-16", service_duration_minutes=60)
print(f"Available slots: {slots}")
# Output: ['09:00', '09:30', '10:00', '11:30', '14:00', '14:30', '15:00', '15:30']
Step 3: The Booking Confirmation Function
After confirming the slot with the customer (via conversation flow), create the calendar event and store it in the CRM:
def book_appointment(calendar_manager, customer_data: dict, slot_datetime: str):
"""
Create a calendar event and return confirmation details.
customer_data: dict with name, phone, service_type
slot_datetime: ISO format datetime string
"""
dt = datetime.fromisoformat(slot_datetime)
end_dt = dt + timedelta(minutes=60) # Default 1-hour appointments
event_body = {
'summary': f"{customer_data['service_type']} - {customer_data['customer_name']}",
'description': (
f"Patient: {customer_data['customer_name']}\n"
f"Phone: {customer_data.get('customer_phone', 'N/A')}\n"
f"Service: {customer_data['service_type']}\n"
f"Booked by: AI Assistant"
),
'start': {
'dateTime': dt.isoformat(),
'timeZone': 'America/Chicago'
},
'end': {
'dateTime': end_dt.isoformat(),
'timeZone': 'America/Chicago'
},
'reminders': {
'useDefault': False,
'overrides': [
{'method': 'popup', 'minutes': 1440}, # 24 hours before
{'method': 'email', 'minutes': 120}, # 2 hours before
]
}
}
event = calendar_manager.service.events().insert(
calendarId=calendar_manager.calendar_id,
body=event_body,
sendUpdates='all'
).execute()
confirmation = {
'event_id': event['id'],
'date': dt.strftime("%A, %B %d, %Y"),
'time': dt.strftime("%I:%M %p"),
'customer_name': customer_data['customer_name'],
'service': customer_data['service_type'],
'html_link': event.get('htmlLink', '')
}
return confirmation
Building the Voice Call Handler with Twilio
For clinics and salons, phone calls remain the dominant booking channel. Here's how to build a Twilio webhook that connects incoming calls to your AI booking engine:
from flask import Flask, request, Response
from twilio.twiml.voice_response import VoiceResponse, Gather
import base64
import json
app = Flask(__name__)
@app.route("/voice/inbound", methods=["POST"])
def handle_inbound_call():
"""
Main entry point for incoming voice calls.
Greets the caller and gathers their spoken request.
"""
resp = VoiceResponse()
# Welcome message
resp.say(
"Thank you for calling Lakeside Dental Clinic. "
"How can I help you schedule your appointment today? "
"Just tell me what service you need and when you'd like to come in.",
voice="Polly.Joanna"
)
# Gather speech input with enhanced recognition settings
gather = Gather(
input="speech",
speechTimeout="auto",
speechModel="phone_call",
enhanced=True,
action="/voice/process_speech",
method="POST",
timeout="4"
)
gather.say("For example, say 'I need a cleaning next Tuesday at 10am'", voice="Polly.Joanna")
resp.append(gather)
# Fallback if no speech detected
resp.say("I didn't catch that. Please call again or visit our website to book online.", voice="Polly.Joanna")
return Response(str(resp), mimetype="text/xml")
@app.route("/voice/process_speech", methods=["POST"])
def process_speech():
"""
Process the caller's spoken request, check availability, and confirm booking.
"""
caller_speech = request.form.get("SpeechResult", "")
caller_phone = request.form.get("From", "")
# Step 1: Extract intent
intent = extract_booking_intent(caller_speech)
# Step 2: Check availability
cal = CalendarManager("service-account.json", "clinic-calendar-id@group.calendar.google.com")
available_slots = cal.find_available_slots(
intent.get("preferred_date"),
service_duration_minutes=60
)
resp = VoiceResponse()
if not available_slots:
resp.say(
f"I'm sorry, but {intent.get('preferred_date')} is fully booked. "
"Would you like to try a different day? Please call back or visit our website.",
voice="Polly.Joanna"
)
return Response(str(resp), mimetype="text/xml")
# Step 3: Present available slots and confirm
first_slot = available_slots[0]
slot_datetime = f"{intent['preferred_date']}T{first_slot}:00"
# Book the first available slot automatically (or use a confirmation gather)
customer_data = {
"customer_name": intent.get("customer_name", "Valued Patient"),
"customer_phone": caller_phone,
"service_type": intent.get("service_type", "General Appointment")
}
confirmation = book_appointment(cal, customer_data, slot_datetime)
# Confirmation message
resp.say(
f"Great news, {confirmation['customer_name']}! "
f"I've booked your {confirmation['service']} on {confirmation['date']} "
f"at {confirmation['time']}. You'll receive a confirmation text and email shortly. "
f"Thank you for calling, and we'll see you soon!",
voice="Polly.Joanna"
)
# Send SMS confirmation via Twilio
send_sms_confirmation(caller_phone, confirmation)
return Response(str(resp), mimetype="text/xml")
def send_sms_confirmation(to_phone: str, confirmation: dict):
"""
Send SMS confirmation using Twilio's messaging API.
"""
from twilio.rest import Client
client = Client("TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN")
message = client.messages.create(
body=(
f"ā
Appointment Confirmed!\n"
f"{confirmation['service']} on {confirmation['date']} at {confirmation['time']}\n"
f"Questions? Call us at (555) 123-4567\n"
f"To reschedule: {confirmation.get('html_link', 'Call us')}"
),
from_="+15551234567",
to=to_phone
)
return message.sid
Multi-Channel Booking: SMS and Web Chat
Not all bookings come via phone. A complete solution should handle SMS and web chat. Here's a unified handler that works across channels:
class MultiChannelBookingAgent:
"""
Unified booking agent that handles SMS, web chat, and voice inputs
through a single conversation pipeline.
"""
def __init__(self, business_config: dict):
self.business_name = business_config.get("name", "Our Clinic")
self.services = business_config.get("services", [])
self.calendar = CalendarManager(
business_config["service_account_file"],
business_config["calendar_id"]
)
self.conversation_states = {} # session_id -> state dict
def handle_message(self, channel: str, session_id: str, message: str, customer_phone: str = None):
"""
Process a booking message from any channel.
channel: 'sms', 'web_chat', or 'voice'
Returns the appropriate response string.
"""
# Get or create conversation state
state = self.conversation_states.get(session_id, {
"step": "greeting",
"collected": {}
})
# Extract intent regardless of step
intent = extract_booking_intent(message)
# Merge collected data
for key in ["customer_name", "customer_phone", "service_type", "preferred_date", "preferred_time"]:
if intent.get(key):
state["collected"][key] = intent[key]
# Determine conversation step
if state["step"] == "greeting":
if not state["collected"].get("service_type"):
return self._prompt_for_service()
state["step"] = "collect_date"
if state["step"] == "collect_date":
if not state["collected"].get("preferred_date"):
return self._prompt_for_date()
state["step"] = "check_availability"
if state["step"] == "check_availability":
slots = self.calendar.find_available_slots(
state["collected"]["preferred_date"]
)
if not slots:
return f"No availability on {state['collected']['preferred_date']}. Please try another date."
state["available_slots"] = slots[:5]
state["step"] = "confirm_slot"
slot_list = ", ".join(state["available_slots"])
return f"Available times on that day: {slot_list}. Which time works best for you?"
if state["step"] == "confirm_slot":
# Try to match a time from the user's message
selected_time = intent.get("preferred_time")
if selected_time and selected_time in state.get("available_slots", []):
state["selected_slot"] = f"{state['collected']['preferred_date']}T{selected_time}:00"
state["step"] = "book"
else:
return f"Please confirm which time slot you'd like from: {', '.join(state['available_slots'])}"
if state["step"] == "book":
customer_data = {
"customer_name": state["collected"].get("customer_name", "Valued Customer"),
"customer_phone": customer_phone,
"service_type": state["collected"].get("service_type", "General Appointment")
}
confirmation = book_appointment(self.calendar, customer_data, state["selected_slot"])
# Clear conversation state
self.conversation_states.pop(session_id, None)
return (
f"ā
All booked! {confirmation['customer_name']}, your "
f"{confirmation['service']} is confirmed for {confirmation['date']} "
f"at {confirmation['time']}. You'll receive a confirmation shortly."
)
# Save state and return
self.conversation_states[session_id] = state
return "I'm here to help you book an appointment. What service are you interested in?"
def _prompt_for_service(self):
services_str = ", ".join([s["name"] for s in self.services])
return f"Welcome to {self.business_name}! What service would you like? We offer: {services_str}."
def _prompt_for_date(self):
return "What date works best for you? You can say something like 'next Tuesday' or 'April 20th'."
Selling to Clinics: The Technical Pitch
Understanding the Buyer
Clinic owners (dentists, physiotherapists, chiropractors) and salon owners have distinct priorities. Clinics worry about HIPAA compliance, integration with practice management software, and insurance verification. Salons care about retail product upselling, stylist rotation, and group bookings. Tailor your demo accordingly.
HIPAA Compliance Layer for Clinics
For medical clinics, you must implement a Business Associate Agreement (BAA) structure and ensure PHI (Protected Health Information) is handled properly. Here's a minimal HIPAA-compliant data handling wrapper:
import hashlib
import secrets
from cryptography.fernet import Fernet
from datetime import datetime, timedelta
class HIPAABookingStore:
"""
HIPAA-compliant appointment storage with encryption at rest,
audit logging, and automatic PHI expiration.
"""
def __init__(self, encryption_key: bytes):
self.cipher = Fernet(encryption_key)
self.audit_log_path = "audit_log.jsonl"
def store_booking(self, booking_data: dict) -> str:
"""
Encrypt PHI fields before storage. Returns a reference ID.
"""
# Separate PHI from non-PHI
phi_fields = ["customer_name", "customer_phone", "customer_email"]
non_phi = {k: v for k, v in booking_data.items() if k not in phi_fields}
phi = {k: v for k, v in booking_data.items() if k in phi_fields}
# Encrypt PHI
encrypted_phi = self.cipher.encrypt(json.dumps(phi).encode())
# Generate reference ID
booking_ref = secrets.token_hex(16)
record = {
"ref_id": booking_ref,
"encrypted_phi": base64.b64encode(encrypted_phi).decode(),
"non_phi": non_phi,
"created_at": datetime.now().isoformat(),
"expires_at": (datetime.now() + timedelta(days=90)).isoformat()
}
# Store in encrypted database (here just printing structure)
print(f"Storing encrypted record: {record['ref_id']}")
# Write audit log
self._write_audit_log("STORED", booking_ref, "Appointment record created")
return booking_ref
def retrieve_booking(self, ref_id: str, accessor_role: str) -> dict:
"""
Decrypt and return booking data. Logs access for audit trail.
"""
self._write_audit_log("ACCESSED", ref_id, f"Accessed by role: {accessor_role}")
# In production, fetch from encrypted DB
# For demo, return structure
return {"ref_id": ref_id, "status": "active"}
def expire_booking(self, ref_id: str):
"""
Automatically purge PHI after retention period (90 days for clinics).
"""
self._write_audit_log("PURGED", ref_id, "PHI data purged per retention policy")
def _write_audit_log(self, action: str, ref_id: str, details: str):
with open(self.audit_log_path, "a") as f:
f.write(json.dumps({
"timestamp": datetime.now().isoformat(),
"action": action,
"ref_id": ref_id,
"details": details
}) + "\n")
# Initialize with a secure key (store in env vars in production)
key = Fernet.generate_key()
hipaa_store = HIPAABookingStore(key)
booking_ref = hipaa_store.store_booking({
"customer_name": "Jane Doe",
"customer_phone": "555-123-4567",
"service_type": "Dental Cleaning",
"appointment_date": "2025-04-20T10:00"
})
print(f"Booking reference: {booking_ref}")
Integration with Common Clinic Platforms
Most clinics use practice management software. Here's a connector for Jane App (widely used by physiotherapy and chiropractic clinics):
import requests
import hashlib
import hmac
import json
class JaneAppConnector:
"""
Sync appointments with Jane App practice management system.
Jane App API docs: https://jane.app/api
"""
def __init__(self, api_key: str, clinic_subdomain: str):
self.api_key = api_key
self.base_url = f"https://{clinic_subdomain}.janeapp.com/api/v1"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def search_patients(self, name: str = None, phone: str = None):
"""Find existing patient by name or phone."""
params = {}
if name:
params["search"] = name
if phone:
params["phone"] = phone
response = requests.get(
f"{self.base_url}/patients",
headers=self.headers,
params=params
)
return response.json()
def create_appointment(self, patient_id: int, clinician_id: int,
appointment_datetime: str, duration_minutes: int,
appointment_type: str):
"""
Create an appointment in Jane App.
Maps to the clinic's actual treatment codes.
"""
payload = {
"appointment": {
"patient_id": patient_id,
"staff_id": clinician_id,
"start_at": appointment_datetime,
"duration": duration_minutes,
"treatment_type": appointment_type,
"status": "booked",
"notes": "Booked via AI Assistant"
}
}
response = requests.post(
f"{self.base_url}/appointments",
headers=self.headers,
json=payload
)
if response.status_code == 201:
return response.json()
else:
raise Exception(f"Jane App error: {response.status_code} - {response.text}")
def get_clinician_availability(self, clinician_id: int, date_str: str):
"""Fetch real availability from Jane App's schedule."""
response = requests.get(
f"{self.base_url}/staff/{clinician_id}/availability",
headers=self.headers,
params={"date": date_str}
)
return response.json()
Building the Sales Demo
When selling to clinics and salons, a live demo is worth more than any slide deck. Here's a self-contained demo script that simulates a full booking flow:
#!/usr/bin/env python3
"""
Self-contained AI booking demo for sales presentations.
Simulates: voice call ā intent extraction ā availability check ā booking ā SMS confirmation.
Run this in front of the prospect to show the full pipeline in under 2 minutes.
"""
import json
import time
from datetime import datetime, timedelta
class DemoBookingPipeline:
def __init__(self, business_name="Demo Salon & Clinic"):
self.business_name = business_name
self.mock_calendar = self._generate_mock_availability()
self.mock_bookings = []
def _generate_mock_availability(self):
"""Create realistic-looking availability for the next 7 days."""
slots = {}
for i in range(1, 8):
date = (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d")
# Simulate some days being busier
if i in [1, 4, 7]:
slots[date] = ["09:00", "10:00", "11:00", "14:00", "15:00"]
else:
slots[date] = ["09:00", "09:30", "10:30", "13:00", "14:30", "16:00"]
return slots
def run_demo(self):
"""Execute a full demo flow with printed output."""
print("=" * 60)
print(f"šÆ AI BOOKING DEMO FOR: {self.business_name}")
print("=" * 60)
# Step 1: Simulate incoming call
caller_speech = "Hi, I'm Maria Garcia. I'd like a haircut and color treatment next Thursday afternoon."
print(f"\nš [INCOMING CALL]: \"{caller_speech}\"")
time.sleep(1)
# Step 2: NLP Processing
print("\nš§ [AI PROCESSING]: Extracting intent...")
intent = {
"customer_name": "Maria Garcia",
"service_type": "haircut and color",
"preferred_date": (datetime.now() + timedelta(days=4)).strftime("%Y-%m-%d"),
"preferred_time": "14:00",
"customer_phone": "+15551234567"
}
print(f" ā Intent extracted: {json.dumps(intent, indent=2)}")
time.sleep(0.5)
# Step 3: Check availability
target_date = intent["preferred_date"]
available = self.mock_calendar.get(target_date, [])
print(f"\nš
[CALENDAR CHECK]: Checking {target_date}...")
print(f" Available slots: {available}")
time.sleep(0.5)
# Step 4: Book best slot
selected_slot = "14:00" if "14:00" in available else available[0]
booking = {
"customer": intent["customer_name"],
"service": intent["service_type"],
"date": target_date,
"time": selected_slot,
"confirmation_id": f"BK-{datetime.now().strftime('%Y%m%d')}-{hash(intent['customer_name']) % 10000:04d}"
}
self.mock_bookings.append(booking)
print(f"\nā
[BOOKED]: {booking['customer']} ā {booking['service']}")
print(f" Date: {booking['date']} at {booking['time']}")
print(f" Confirmation: {booking['confirmation_id']}")
time.sleep(0.5)
# Step 5: SMS confirmation
sms_body = (
f"ā
{self.business_name} Appointment Confirmed!\n"
f"{booking['service']} ā {booking['date']} at {booking['time']}\n"
f"Confirmation #{booking['confirmation_id']}\n"
f"Reply CANCEL to cancel. We look forward to seeing you!"
)
print(f"\nš± [SMS SENT to {intent['customer_phone']}]:")
print(f" {sms_body}")
time.sleep(0.5)
# Step 6: Summary for business owner
print("\n" + "=" * 60)
print("š BUSINESS OWNER DASHBOARD")
print("=" * 60)
print(f"Today's AI Bookings: {len(self.mock_bookings)}")
print(f"Estimated Revenue: ${len(self.mock_bookings) * 85:.2f}")
print(f"Staff Hours Saved: {len(self.mock_bookings) * 0.5:.1f} hours")
print(f"Missed Calls Eliminated: 100% of after-hours calls captured")
print("=" * 60)
print("\nš Demo complete! The entire flow took under 3 seconds of AI processing time.")
# Run the demo
demo = DemoBookingPipeline("Glo Wellness Spa & Clinic")
demo.run_demo()
Pricing and Packaging Strategy
The Tiered Model That Works
After working with dozens of clinics and salons, this pricing structure consistently converts:
- Starter ($99/month) ā AI web chat booking widget + email confirmations. 100 appointments/month. Good for small salons testing the waters.
- Professional ($299/month) ā Voice + SMS + Web. 500 appointments/month. Includes calendar sync and reminder automation. The sweet spot for most clinics.
- Enterprise ($599/month) ā Unlimited appointments, HIPAA-compliant storage, CRM integration (Jane App, Mindbody, etc.), custom voice branding, multi-location support.
Onboarding Fee Strategy
Charge a one-time setup fee of $500-1,500 that covers calendar integration, voice menu customization, and staff training. This filters out non-serious prospects and funds your integration work. For enterprise deals with custom CRM connectors, charge $2,500-5,000 setup.
Best Practices for Production Deployment
1. Voice Menu Customization
Every clinic and salon needs a branded voice experience. Build a configuration portal where owners can customize greetings, service names, and business hours without touching code:
# Configuration schema for business onboarding
BUSINESS_CONFIG_SCHEMA = {
"business_name": "string",
"phone_number": "string",
"calendar_id": "string",
"business_hours": {
"monday": {"open": "09:00", "close": "17:00"},
"tuesday": {"open": "09:00", "close": "17:00"},
"wednesday": {"open": "09:00", "close": "17:00"},
"thursday": {"open": "09:00", "close": "17:00"},
"friday": {"open": "09:00", "close": "17:00"},
"saturday": {"open": "10:00", "close": "15:00"},
"sunday": {"open": None, "close": None} # Closed
},
"services": [
{"name": "Dental Cleaning", "duration_minutes": 60, "price": 120},
{"name": "Teeth Whitening", "duration_minutes": 90, "price": 350},
{"name": "Consultation", "duration_minutes": 30, "price": 75}
],
"voice_greeting": "Thank you for calling {business_name}. How can I help you today?",
"sms_templates": {
"confirmation": "ā
Your {service} appointment is confirmed for {date} at {time}.",
"reminder_24h": "ā° Reminder: Your {service} appointment tomorrow at {time} with {business_name}.",
"reminder_2h": "ā° Your {service} appointment starts in 2 hours at {business_name}."
}
}
2. Handling Edge Cases Gracefully
Your AI must handle these common scenarios without sounding robotic:
- Double-booking prevention ā Use atomic calendar writes with ETags to prevent race conditions
- Time zone awareness ā Store everything in UTC, display in the business's local time zone
- Cancellation and rescheduling ā Support "cancel my appointment" and "move my appointment to Friday" via the same NLP pipeline
- Multi-service grouping ā When a customer wants "haircut, color, and blowout," calculate total