API Reference
Waitlist

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

FieldTypeDescription
idUUIDUnique identifier
client_idUUIDClient waiting
service_idUUIDDesired service (optional)
preferred_provider_idUUIDPreferred provider (optional)
preferred_date_startDateEarliest acceptable date
preferred_date_endDateLatest acceptable date
preferred_time_startTimeEarliest time of day
preferred_time_endTimeLatest time of day
preferred_daysInteger[]Days of week (0=Sun, 6=Sat)
notesStringAdditional preferences
statusEnumCurrent status
offered_appointment_idUUIDAppointment offered
offered_atDateTimeWhen offer was made
booked_appointment_idUUIDFinal booked appointment
booked_atDateTimeWhen booked
expires_atDateTimeWhen entry expires
created_byUUIDStaff who added
created_atDateTimeWhen added

Status Values

StatusDescription
waitingWaiting for availability
offeredSlot offered, awaiting response
bookedSuccessfully booked
expiredEntry expired without booking
cancelledClient 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 + offered
  • conversionRate — 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

  1. Capture preferences — More data = better matches
  2. Set expiration dates — Don't keep stale entries
  3. Prioritize fairly — First-come or by urgency
  4. Quick response — Time-sensitive opportunities
  5. Track metrics — Measure fill rate and wait times
  6. Automate notifications — SMS works best for urgency
  7. Follow up on offers — Don't let offers expire without contact