Skip to content
Learn Agentic AI12 min read0 views

AI Agent for Appointment-Based Businesses: Salons, Spas, and Professional Services

Build an AI scheduling agent that handles appointment booking, cancellations, reminders, rebooking, and waitlist management for salons, spas, and service-based small businesses.

Why Appointment Scheduling Is the Perfect AI Use Case

For salons, spas, massage therapists, and professional service firms, the phone rings constantly with the same request: "Can I book an appointment?" Staff spend hours each day on scheduling tasks that follow predictable rules — checking availability, matching services to providers, sending confirmations. An AI agent handles these interactions instantly, freeing staff to focus on the clients who are already in the chair.

This tutorial walks through building a complete scheduling agent with booking, cancellation, reminder, rebooking, and waitlist capabilities.

Data Model for Scheduling

A solid scheduling agent starts with a clear data model. We need to represent services, providers, time slots, and appointments.

from dataclasses import dataclass, field
from datetime import datetime, date, time, timedelta
from enum import Enum
from typing import Optional
import uuid


class AppointmentStatus(Enum):
    CONFIRMED = "confirmed"
    CANCELLED = "cancelled"
    COMPLETED = "completed"
    NO_SHOW = "no_show"
    WAITLISTED = "waitlisted"


@dataclass
class Service:
    id: str
    name: str
    duration_minutes: int
    price: float
    category: str
    providers: list[str]  # provider IDs who can perform this service


@dataclass
class TimeSlot:
    provider_id: str
    start: datetime
    end: datetime
    is_available: bool = True


@dataclass
class Appointment:
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    client_name: str = ""
    client_phone: str = ""
    service_id: str = ""
    provider_id: str = ""
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    status: AppointmentStatus = AppointmentStatus.CONFIRMED
    notes: str = ""
    reminder_sent: bool = False

Availability Engine

The availability engine is the heart of the scheduling agent. It must check provider schedules, account for existing appointments, respect buffer times between appointments, and handle lunch breaks.

class AvailabilityEngine:
    def __init__(self):
        self.appointments: list[Appointment] = []
        self.provider_schedules: dict[str, dict] = {}
        self.buffer_minutes: int = 15  # gap between appointments

    def set_provider_schedule(
        self, provider_id: str, day: str,
        start: time, end: time, lunch_start: time, lunch_end: time
    ):
        if provider_id not in self.provider_schedules:
            self.provider_schedules[provider_id] = {}
        self.provider_schedules[provider_id][day] = {
            "start": start, "end": end,
            "lunch_start": lunch_start, "lunch_end": lunch_end,
        }

    def get_available_slots(
        self, provider_id: str, target_date: date,
        duration_minutes: int
    ) -> list[TimeSlot]:
        day_name = target_date.strftime("%A").lower()
        schedule = self.provider_schedules.get(provider_id, {}).get(day_name)
        if not schedule:
            return []

        day_start = datetime.combine(target_date, schedule["start"])
        day_end = datetime.combine(target_date, schedule["end"])
        lunch_start = datetime.combine(target_date, schedule["lunch_start"])
        lunch_end = datetime.combine(target_date, schedule["lunch_end"])

        existing = sorted(
            [a for a in self.appointments
             if a.provider_id == provider_id
             and a.start_time and a.start_time.date() == target_date
             and a.status == AppointmentStatus.CONFIRMED],
            key=lambda a: a.start_time,
        )

        slots = []
        current = day_start
        duration = timedelta(minutes=duration_minutes)
        buffer = timedelta(minutes=self.buffer_minutes)

        while current + duration <= day_end:
            slot_end = current + duration
            # Skip lunch
            if current < lunch_end and slot_end > lunch_start:
                current = lunch_end
                continue
            # Check conflicts with existing appointments
            conflict = False
            for appt in existing:
                appt_start = appt.start_time - buffer
                appt_end = appt.end_time + buffer
                if current < appt_end and slot_end > appt_start:
                    conflict = True
                    current = appt.end_time + buffer
                    break
            if not conflict:
                slots.append(TimeSlot(
                    provider_id=provider_id,
                    start=current, end=slot_end
                ))
                current += timedelta(minutes=30)  # 30-min increments

        return slots

Agent Tools for Booking Operations

We expose the scheduling engine to the agent through function tools that handle each booking operation.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

from agents import Agent, Runner, function_tool

engine = AvailabilityEngine()

SERVICES = {
    "haircut": Service("s1", "Haircut", 30, 35.0, "hair", ["p1", "p2"]),
    "color": Service("s2", "Color Treatment", 90, 120.0, "hair", ["p1"]),
    "massage": Service("s3", "Swedish Massage", 60, 85.0, "body", ["p3"]),
}


@function_tool
def check_availability(
    service_name: str, preferred_date: str,
    preferred_provider: str = ""
) -> str:
    """Check available appointment slots for a service on a given date."""
    service = SERVICES.get(service_name.lower())
    if not service:
        return f"Service '{service_name}' not found. Available: {list(SERVICES.keys())}"
    target = date.fromisoformat(preferred_date)
    providers = [preferred_provider] if preferred_provider else service.providers
    results = []
    for pid in providers:
        slots = engine.get_available_slots(pid, target, service.duration_minutes)
        for slot in slots[:5]:
            results.append(f"{pid}: {slot.start.strftime('%I:%M %p')}")
    return "\n".join(results) if results else "No availability on that date."


@function_tool
def book_appointment(
    client_name: str, client_phone: str,
    service_name: str, provider_id: str, slot_time: str
) -> str:
    """Book an appointment for a client."""
    service = SERVICES.get(service_name.lower())
    start = datetime.fromisoformat(slot_time)
    end = start + timedelta(minutes=service.duration_minutes)
    appt = Appointment(
        client_name=client_name, client_phone=client_phone,
        service_id=service.id, provider_id=provider_id,
        start_time=start, end_time=end,
    )
    engine.appointments.append(appt)
    return f"Booked: {service.name} with {provider_id} at {start.strftime('%I:%M %p')} on {start.strftime('%B %d')}. Confirmation ID: {appt.id[:8]}"


@function_tool
def cancel_appointment(confirmation_id: str, reason: str = "") -> str:
    """Cancel an existing appointment by confirmation ID."""
    for appt in engine.appointments:
        if appt.id.startswith(confirmation_id):
            appt.status = AppointmentStatus.CANCELLED
            appt.notes = f"Cancelled: {reason}"
            return f"Appointment {confirmation_id} cancelled. Would you like to rebook?"
    return "Appointment not found. Please check your confirmation ID."

Waitlist Management

When preferred slots are taken, the agent should offer waitlist placement rather than losing the booking entirely.

waitlist: list[dict] = []

@function_tool
def join_waitlist(
    client_name: str, client_phone: str,
    service_name: str, preferred_date: str
) -> str:
    """Add a client to the waitlist for a fully booked date."""
    waitlist.append({
        "client_name": client_name,
        "client_phone": client_phone,
        "service": service_name,
        "date": preferred_date,
        "added_at": datetime.now().isoformat(),
    })
    return (
        f"{client_name} added to the waitlist for {service_name} "
        f"on {preferred_date}. We will call if a slot opens up."
    )

Assembling the Scheduling Agent

scheduling_agent = Agent(
    name="Salon Scheduling Agent",
    instructions="""You are a friendly scheduling assistant for a salon and spa.

1. When a client wants to book, ask which service they need and their preferred date.
2. Use check_availability to find open slots, then present the top 3 options.
3. Once the client picks a slot, collect their name and phone, then book_appointment.
4. If no slots are available, offer to join_waitlist.
5. For cancellations, ask for the confirmation ID and the reason.
6. Always confirm the final details before booking or cancelling.
7. Mention the service price when presenting options.""",
    tools=[check_availability, book_appointment, cancel_appointment, join_waitlist],
)

FAQ

How do I send automated appointment reminders?

Run a background scheduler (using APScheduler or a cron job) that queries appointments 24 hours before their start time. For each appointment where reminder_sent is False, send an SMS or email, then set the flag to True. The agent itself does not need to handle this — it is a separate async process.

What happens if two people try to book the same slot simultaneously?

In production, wrap the booking operation in a database transaction with a row-level lock on the time slot. If the slot was already claimed between the availability check and the booking attempt, return an error and offer the next available slot. This is a standard optimistic concurrency pattern.

Can the agent handle multi-service bookings like "haircut and color"?

Yes. Extend the book_appointment tool to accept a list of service IDs, sum the durations, and find a contiguous block of availability. The agent instructions should tell it to ask whether the client wants to combine services with the same provider or split across providers.


#AppointmentScheduling #SmallBusiness #AIAgent #BookingSystem #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

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.