{"slug":"spa-booking-multi-therapist-system","title":"Building a Multi-Therapist Spa Booking System on AvailEngine","excerpt":"A spa booking system needs to handle multiple therapists with different specialities, varying service durations, overlapping availability, and online booking with deposit protection. Here's how I modelled each therapist as a resource, mapped services to durations, and built a booking flow that a real spa in Glyfada uses every day.","date":"July 10, 2026","category":"Tutorial","categoryColor":"bg-purple-50 text-purple-700","keywords":"spa booking system, multi-therapist scheduling, service booking API, spa management software","content_html":"<pre>A spa in Glyfada asked me to build them a booking system. They have 6 therapists, 3 treatment rooms, and 12 different services ranging from a 30-minute express facial to a 2-hour full-body aromatherapy massage. Every therapist has their own schedule, their own set of services they're qualified to perform, and their own personal brand — regular clients book specific people, not just time slots.\n\n### The Domain Model\n\nThe key insight is that AvailEngine's resource model maps perfectly to this. Each therapist is a **resource** with capacity 1 — they can serve one client at a time. Treatment rooms are also resources, but since the spa already has one-therapist-per-room, I only needed to track the therapists.\n\nEach service type translates to a booking duration. A 30-minute facial gets a 30-minute slot. The 2-hour full-body massage gets a 120-minute slot with a 15-minute buffer afterward for cleanup and setup:\n\n- **Express Facial (30 min)** — short slot, high turnover, any therapist\n- **Deep Tissue Massage (60 min)** — medium slot, only therapists trained in sports massage\n- **Aromatherapy Full Body (120 min)** — long slot, all senior therapists, 15-min cleanup buffer\n- **Hot Stone Therapy (90 min)** — requires specific certification, 2 therapists qualified\n\n### Therapist Qualification Filtering\n\nNot every therapist can perform every service. I store each therapist's qualifications as metadata and filter the availability call by `resource_id`. When a client selects \"Hot Stone Therapy,\" the front-end filters to only show slots from the two qualified therapists:\n\n```\n// Fetch availability for qualified therapists only\nconst qualifiedTherapists = ['therapist-3', 'therapist-5']\n\nconst slots = await Promise.all(\n  qualifiedTherapists.map(id =>\n    fetch(`https://api.availengine.com/v1/availability/${spaId}\n      ?date=${date}&resource_id=${id}&duration=90`)\n      .then(r => r.json())\n  )\n)\n\n// Merge and display: \"Maria has 4 slots available today\"\nslots.forEach((s, i) => {\n  const therapist = therapists[qualifiedTherapists[i]]\n  if (s.available_slots.length > 0) {\n    display(therapist.name, s.available_slots)\n  }\n})\n```\n\n### Online Booking Flow\n\nThe booking flow is a 3-step wizard. Step 1: pick a service (this sets the duration). Step 2: pick a therapist (this filters to their available slots). Step 3: pick a time and pay a €5 deposit to hold it. The whole flow took about 150 lines of React:\n\n```\nPOST /v1/bookings/\n{\n  \"business_id\": spaId,\n  \"resource_id\": therapistId,\n  \"start_time\": \"2026-07-10T14:00:00Z\",\n  \"end_time\": \"2026-07-10T15:30:00Z\",\n  \"customer\": {\n    \"name\": \"Elena K.\",\n    \"email\": \"elena@example.com\",\n    \"phone\": \"+30 69X XXXXXXX\"\n  },\n  \"deposit_cents\": 500,\n  \"notes\": \"First time client — prefer light pressure\"\n}\n```\n\nThe booking is created as **pending** until Stripe confirms the deposit payment via webhook. Once confirmed, it transitions to **confirmed** and the therapist's calendar blocks that slot in real time — no double-booking possible.\n\n### Staff Dashboard\n\nThe spa manager gets a staff-facing dashboard built on the management endpoints. They can see all bookings for the day grouped by therapist, check-in clients when they arrive, and handle no-shows with a single click — which captures the deposit as compensation for the missed slot:\n\n```\n// Staff check-in\nPATCH /v1/manage/bookings/{id}\n{ \"status\": \"checked_in\" }\n\n// No-show — deposit is captured automatically\nPOST /v1/manage/bookings/{id}/no-show\n```\n\nThe spa has been live for three months. No-shows dropped from about 4 per week to 1 every other week, and the online booking feature brought in 30% more new clients who found them through Google and booked directly instead of calling.\n\nThe whole thing — from API key to live bookings — took an afternoon. If you can model it as people with schedules and services with durations, you can build a spa booking system on AvailEngine. Start here.</pre>"}