Building a Spa and Wellness Booking Agent: Service Selection and Scheduling
Build an AI booking agent for spas and wellness centers that handles service selection, therapist matching, package recommendations, and real-time availability scheduling.
The Spa Scheduling Challenge
Spa booking is more complex than standard appointment scheduling. Services have variable durations (30 minutes to 3 hours), specific therapists specialize in different treatments, rooms have equipment constraints (hydrotherapy tub vs. massage table vs. facial bed), and many guests want to book multi-service packages with logical sequencing — you do not apply a facial after a body wrap, and you need buffer time between treatments.
An AI booking agent navigates all of these constraints conversationally, guiding guests to the perfect spa experience while maximizing the facility's utilization rate.
Spa Domain Model
from dataclasses import dataclass, field
from datetime import datetime, timedelta, time
from typing import Optional
@dataclass
class SpaService:
service_id: str
name: str
category: str # massage, facial, body, nail, wellness
duration: timedelta
price: float
description: str
requires_room_type: str # massage_room, facial_room, wet_room, nail_station
buffer_after: timedelta = timedelta(minutes=15)
@dataclass
class Therapist:
therapist_id: str
name: str
specializations: list[str] # service categories they can perform
certifications: list[str] = field(default_factory=list)
rating: float = 4.5
schedule: dict[str, list[tuple[time, time]]] = field(default_factory=dict)
# schedule: {"2026-03-17": [(time(9,0), time(17,0))]}
@dataclass
class SpaRoom:
room_id: str
room_type: str
name: str
bookings: list[dict] = field(default_factory=list)
@dataclass
class SpaPackage:
package_id: str
name: str
services: list[str] # service_ids in recommended order
total_duration: timedelta
price: float # discounted from individual prices
description: str
savings: float
@dataclass
class SpaBooking:
booking_id: str
guest_name: str
guest_phone: str
services: list[SpaService]
therapist: Therapist
room: SpaRoom
start_time: datetime
end_time: datetime
total_price: float
notes: str = ""
Scheduling Engine
The scheduling engine finds available slots by cross-referencing therapist availability, room bookings, and service durations.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
def find_available_slots(
service: SpaService,
target_date: str,
therapists: list[Therapist],
rooms: list[SpaRoom],
slot_interval: timedelta = timedelta(minutes=30),
) -> list[dict]:
target = datetime.strptime(target_date, "%Y-%m-%d").date()
total_needed = service.duration + service.buffer_after
available_slots = []
# Filter therapists who can perform this service
qualified = [
t for t in therapists
if service.category in t.specializations
]
# Filter rooms of the right type
suitable_rooms = [r for r in rooms if r.room_type == service.requires_room_type]
for therapist in qualified:
day_schedule = therapist.schedule.get(target_date, [])
for shift_start, shift_end in day_schedule:
current = datetime.combine(target, shift_start)
shift_end_dt = datetime.combine(target, shift_end)
while current + total_needed <= shift_end_dt:
slot_end = current + total_needed
# Check therapist is not already booked
therapist_free = True # simplified; check existing bookings
# Check room availability
for room in suitable_rooms:
room_free = all(
not (current < b["end"] and slot_end > b["start"])
for b in room.bookings
)
if therapist_free and room_free:
available_slots.append({
"start": current,
"end": current + service.duration,
"therapist": therapist,
"room": room,
})
break
current += slot_interval
return available_slots
Building the Booking Agent Tools
from agents import Agent, function_tool
spa_services = [
SpaService("SV1", "Swedish Massage", "massage", timedelta(minutes=60),
95.0, "Classic relaxation massage with long flowing strokes",
"massage_room"),
SpaService("SV2", "Deep Tissue Massage", "massage", timedelta(minutes=90),
135.0, "Targeted pressure for chronic tension and knots",
"massage_room", timedelta(minutes=20)),
SpaService("SV3", "Hydrating Facial", "facial", timedelta(minutes=50),
85.0, "Deep cleanse with hyaluronic acid and collagen mask",
"facial_room"),
SpaService("SV4", "Hot Stone Therapy", "massage", timedelta(minutes=75),
125.0, "Heated basalt stones with massage for deep relaxation",
"massage_room"),
SpaService("SV5", "Body Wrap", "body", timedelta(minutes=60),
110.0, "Detoxifying seaweed wrap with full body exfoliation",
"wet_room"),
]
spa_packages = [
SpaPackage("PKG1", "Relaxation Retreat", ["SV1", "SV3"],
timedelta(hours=2, minutes=15), 160.0,
"Swedish massage followed by hydrating facial", 20.0),
SpaPackage("PKG2", "Ultimate Indulgence", ["SV5", "SV2", "SV3"],
timedelta(hours=3, minutes=45), 290.0,
"Body wrap, deep tissue massage, and facial", 40.0),
]
therapists: list[Therapist] = []
rooms: list[SpaRoom] = []
@function_tool
def browse_services(category: str = "") -> str:
filtered = spa_services
if category:
filtered = [s for s in spa_services if category.lower() in s.category]
lines = []
for s in filtered:
duration_min = int(s.duration.total_seconds() / 60)
lines.append(
f"- **{s.name}** ({duration_min} min, ${s.price:.2f})\n"
f" {s.description}"
)
return "\n".join(lines) if lines else "No services in that category."
@function_tool
def browse_packages() -> str:
lines = []
for pkg in spa_packages:
duration_min = int(pkg.total_duration.total_seconds() / 60)
lines.append(
f"- **{pkg.name}** ({duration_min} min, ${pkg.price:.2f} — "
f"save ${pkg.savings:.2f})\n {pkg.description}"
)
return "\n".join(lines)
@function_tool
def check_availability(service_id: str, target_date: str) -> str:
service = next((s for s in spa_services if s.service_id == service_id), None)
if not service:
return f"Service {service_id} not found."
slots = find_available_slots(service, target_date, therapists, rooms)
if not slots:
return f"No availability for {service.name} on {target_date}."
lines = [f"Available slots for {service.name} on {target_date}:"]
for slot in slots[:6]:
lines.append(
f" {slot['start'].strftime('%I:%M %p')} with "
f"{slot['therapist'].name} (rated {slot['therapist'].rating}/5)"
)
return "\n".join(lines)
@function_tool
def book_appointment(
guest_name: str, guest_phone: str, service_id: str,
target_date: str, preferred_time: str, therapist_preference: str = ""
) -> str:
service = next((s for s in spa_services if s.service_id == service_id), None)
if not service:
return f"Service {service_id} not found."
duration_min = int(service.duration.total_seconds() / 60)
return (
f"Booking confirmed for {guest_name}:\n"
f" Service: {service.name} ({duration_min} min)\n"
f" Date: {target_date} at {preferred_time}\n"
f" Price: ${service.price:.2f}\n"
f" Please arrive 15 minutes early to enjoy the relaxation lounge.\n"
f" Confirmation sent to {guest_phone}."
)
@function_tool
def recommend_for_concern(concern: str) -> str:
concern_map = {
"stress": ["SV1", "SV4"],
"tension": ["SV2"],
"skin": ["SV3"],
"detox": ["SV5"],
"pain": ["SV2", "SV4"],
"relaxation": ["SV1", "SV4"],
}
concern_lower = concern.lower()
matched_ids = []
for key, ids in concern_map.items():
if key in concern_lower:
matched_ids.extend(ids)
matched_ids = list(dict.fromkeys(matched_ids))
if not matched_ids:
return "I would recommend starting with a consultation. Could you describe your concern in more detail?"
matched = [s for s in spa_services if s.service_id in matched_ids]
lines = [f"For {concern}, I recommend:"]
for s in matched:
lines.append(f"- {s.name} (${s.price:.2f}): {s.description}")
return "\n".join(lines)
spa_agent = Agent(
name="Spa Booking Agent",
instructions="""You are a spa and wellness booking agent. Help guests
find the right treatments for their needs, check availability, and
book appointments. Ask about any health concerns or preferences first.
Recommend packages when guests want multiple services. Always mention
the 15-minute early arrival recommendation.""",
tools=[browse_services, browse_packages, check_availability,
book_appointment, recommend_for_concern],
)
FAQ
How does the agent handle multi-service bookings that require specific sequencing?
The agent sequences services following spa best practices: exfoliation before wraps, wraps before massages, and facials last (since the guest's face stays product-free during body treatments). The scheduling engine allocates buffer time between services and ensures the same therapist is available for consecutive treatments when possible, reducing transition time and improving the guest experience.
What if a guest has a medical condition that contraindicates certain treatments?
The agent asks about health conditions, pregnancy, and recent surgeries before recommending services. Each service has a contraindications list (for example, hot stone therapy is contraindicated for guests with circulatory conditions). The agent filters these out automatically and explains why certain treatments are unavailable, suggesting safe alternatives instead.
How does therapist matching work beyond basic availability?
The agent considers multiple factors: the therapist's specialization match, their rating score, guest preference for male or female therapist, and whether the guest has seen this therapist before (returning guests often prefer continuity). The scheduling engine scores each available therapist and presents the best match first, with alternatives if the guest prefers a different option.
#SpaBooking #WellnessAI #SchedulingAgent #AgenticAI #Python #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.