Waitlist API
Manage appointment waitlists and fill cancellations efficiently.
Overview
The Waitlist API helps you:
- Capture demand when slots are unavailable
- Match clients to openings when cancellations occur
- Automate outreach for available slots
- Track waitlist conversion rates
Data Model
Waitlist Entry
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
client_id | UUID | Client waiting |
service_id | UUID | Desired service (optional) |
preferred_provider_id | UUID | Preferred provider (optional) |
preferred_date_start | Date | Earliest acceptable date |
preferred_date_end | Date | Latest acceptable date |
preferred_time_start | Time | Earliest time of day |
preferred_time_end | Time | Latest time of day |
preferred_days | Integer[] | Days of week (0=Sun, 6=Sat) |
notes | String | Additional preferences |
status | Enum | Current status |
offered_appointment_id | UUID | Appointment offered |
offered_at | DateTime | When offer was made |
booked_appointment_id | UUID | Final booked appointment |
booked_at | DateTime | When booked |
expires_at | DateTime | When entry expires |
created_by | UUID | Staff who added |
created_at | DateTime | When added |
Status Values
| Status | Description |
|---|---|
waiting | Waiting for availability |
offered | Slot offered, awaiting response |
booked | Successfully booked |
expired | Entry expired without booking |
cancelled | Client cancelled from waitlist |
Actions
Get Waitlist
import { getWaitlist } from '@/lib/actions/waitlist'
// All waiting entries
const result = await getWaitlist({
status: 'waiting'
})
// Filter by service
const botoxWaitlist = await getWaitlist({
service_id: 'botox-service-uuid',
status: ['waiting', 'offered']
})
// Filter by provider
const drJohnsonWaitlist = await getWaitlist({
provider_id: 'provider-uuid'
})
// Client's waitlist history
const clientHistory = await getWaitlist({
client_id: 'client-uuid'
})Get Single Entry
import { getWaitlistEntry } from '@/lib/actions/waitlist'
const result = await getWaitlistEntry('entry-uuid')Add to Waitlist
import { addToWaitlist } from '@/lib/actions/waitlist'
// Basic entry
const result = await addToWaitlist({
client_id: 'client-uuid',
service_id: 'service-uuid',
notes: 'Wants earliest available'
})
// With preferences
const detailed = await addToWaitlist({
client_id: 'client-uuid',
service_id: 'service-uuid',
preferred_provider_id: 'provider-uuid',
preferred_date_start: '2026-02-15',
preferred_date_end: '2026-02-28',
preferred_time_start: '09:00',
preferred_time_end: '14:00',
preferred_days: [1, 2, 3, 4, 5], // Mon-Fri only
notes: 'No weekends, mornings preferred',
expires_at: '2026-03-01T00:00:00Z'
})Update Entry
import { updateWaitlistEntry } from '@/lib/actions/waitlist'
const result = await updateWaitlistEntry('entry-uuid', {
preferred_date_end: '2026-03-15', // Extended window
notes: 'Now available on weekends too'
})Remove from Waitlist
import { removeFromWaitlist } from '@/lib/actions/waitlist'
const result = await removeFromWaitlist('entry-uuid')
// Sets status to 'cancelled'Offering Appointments
Offer an Appointment
When a slot opens up, offer it to someone on the waitlist.
import { offerAppointment } from '@/lib/actions/waitlist'
const result = await offerAppointment(
'waitlist-entry-uuid',
'appointment-uuid' // The appointment to offer
)This:
- Changes status to
offered - Records the offered appointment
- Records when the offer was made
Accept Offer
Client accepts the offered appointment.
import { acceptOffer } from '@/lib/actions/waitlist'
const result = await acceptOffer('waitlist-entry-uuid')This:
- Changes status to
booked - Records the booked appointment
- Records booking timestamp
Decline Offer
Client declines and wants to stay on waitlist.
import { declineOffer } from '@/lib/actions/waitlist'
const result = await declineOffer('waitlist-entry-uuid')This:
- Returns status to
waiting - Clears the offered appointment
- Entry remains on waitlist for future offers
Finding Matches
Find Matching Slots
Find available appointments that match a waitlist entry's preferences.
import { findMatchingSlots } from '@/lib/actions/waitlist'
const result = await findMatchingSlots('waitlist-entry-uuid', {
startDate: '2026-02-15',
endDate: '2026-02-28',
limit: 10
})Response:
{
"success": true,
"data": [
{
"date": "2026-02-17",
"provider_id": "uuid",
"provider_name": "Dr. Johnson",
"slots": [
{ "start": "2026-02-17T09:00:00Z", "end": "2026-02-17T10:00:00Z" },
{ "start": "2026-02-17T10:00:00Z", "end": "2026-02-17T11:00:00Z" }
]
},
{
"date": "2026-02-19",
"provider_id": "uuid",
"provider_name": "Dr. Johnson",
"slots": [
{ "start": "2026-02-19T09:00:00Z", "end": "2026-02-19T10:00:00Z" }
]
}
]
}The function:
- Respects preferred date range
- Filters by preferred time window
- Only checks preferred days of week
- Uses the service's duration
- Checks provider availability (if specified)
Notifications
Find Clients to Notify
When a cancellation occurs, find waitlist clients to notify.
import { notifyWaitlistClients } from '@/lib/actions/waitlist'
const result = await notifyWaitlistClients(
'service-uuid',
'2026-02-20' // The available date
)Response:
{
"success": true,
"data": {
"clients": [
{
"id": "waitlist-uuid",
"client": {
"id": "client-uuid",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+15551234567"
}
}
],
"message": "Found 3 clients to notify about availability on 2026-02-20"
}
}You can then integrate with SMS/email to send notifications.
Statistics
Get Waitlist Stats
import { getWaitlistStats } from '@/lib/actions/waitlist'
const result = await getWaitlistStats()Response:
{
"success": true,
"data": {
"counts": {
"waiting": 15,
"offered": 3,
"booked": 42,
"expired": 8,
"cancelled": 5
},
"total": 73,
"activeCount": 18,
"conversionRate": 58,
"averageWaitDays": 7
}
}activeCount— waiting + offeredconversionRate— booked / total (%)averageWaitDays— Average days from joining to booking
Maintenance
Expire Old Entries
Automatically expire entries that have been waiting too long.
import { expireOldEntries } from '@/lib/actions/waitlist'
// Expire entries older than 30 days
const result = await expireOldEntries(30)
// Returns count of expired entries
// { expired: 5 }This can be run as a scheduled job (cron).
Workflow Example
Cancellation Fills Waitlist
async function handleCancellation(appointmentId: string) {
const appointment = await getAppointment(appointmentId)
// 1. Cancel the appointment
await cancelAppointment(appointmentId, 'Client cancelled')
// 2. Find waitlist matches for this slot
const matches = await notifyWaitlistClients(
appointment.service_id,
appointment.scheduled_at.split('T')[0]
)
// 3. Notify the first match
if (matches.data.clients.length > 0) {
const firstMatch = matches.data.clients[0]
// Create a new appointment for the waitlist client
const newAppt = await createAppointment({
client_id: firstMatch.client.id,
service_id: appointment.service_id,
provider_id: appointment.provider_id,
scheduled_at: appointment.scheduled_at,
duration_minutes: appointment.duration_minutes
})
// Offer it to them
await offerAppointment(firstMatch.id, newAppt.data.id)
// Send notification (SMS/email)
await sendSMS(firstMatch.client.phone,
`Great news! An appointment opened up for ${appointment.service.name} ` +
`on ${formatDate(appointment.scheduled_at)}. Reply YES to confirm.`
)
}
}Best Practices
- Capture preferences — More data = better matches
- Set expiration dates — Don't keep stale entries
- Prioritize fairly — First-come or by urgency
- Quick response — Time-sensitive opportunities
- Track metrics — Measure fill rate and wait times
- Automate notifications — SMS works best for urgency
- Follow up on offers — Don't let offers expire without contact