GlowBook AI

GlowBook API v1

The public REST API lets a WhatsApp assistant, a website widget or any internal tool read a salon's catalogue and availability and create, move and cancel bookings. Everything is scoped to the salon (tenant) that owns the API key. This page is served live at /api/v1/docs.

Basics

All examples below use the demo salon Pageant Lashes (pageant-lashes). Replace $KEY with your key and $BASE with your base URL.

export BASE=http://localhost:3000/api/v1
export KEY=gbk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

GET /me

Public info about the salon plus the booking rules the API enforces.

curl -s $BASE/me -H "Authorization: Bearer $KEY"
{
  "id": "…", "name": "Pageant Lashes by Serenity Beaute", "slug": "pageant-lashes",
  "timezone": "Asia/Singapore", "currency": "SGD", "locale": "en-SG",
  "phone": "+65 8938 2624", "whatsapp": "6589382624", "address": "10 Anson Road #02-13, …",
  "bookingUrl": "http://localhost:3000/book/pageant-lashes",
  "bookingSettings": { "minLeadMinutes": 120, "maxAdvanceDays": 60, "slotStepMin": 15, "cancelPolicyHours": 24, "confirmMode": "auto", "requirePhone": true, "requireEmail": false, "cancellationPolicyText": "…" },
  "apiKey": { "name": "SmartFAQ", "prefix": "gbk_live_abcd", "scopes": ["read", "write"] }
}

GET /services

Categories with their services, add-ons and the staff who can perform each one. Services with visibility: "hidden" are omitted unless ?includeHidden=1 (needs write). visibility: "members" services are returned so a bot can explain them, but online booking rules still apply.

curl -s "$BASE/services" -H "Authorization: Bearer $KEY"
{
  "currency": "SGD",
  "categories": [
    { "id": "…", "name": "Lashes — First Trial", "visibility": "public",
      "services": [
        { "id": "svc_1", "name": "Volume 5-7D Glorious Lashes", "durationMin": 90, "bufferAfterMin": 10,
          "priceCents": 10800, "compareAtCents": 14800, "depositCents": 0, "firstVisitOnly": true,
          "visibility": "public", "bookableOnline": true, "rebookIntervalDays": 21,
          "addons": [ { "id": "add_1", "name": "Lower Lash Extension", "durationMin": 20, "priceCents": 2800 } ],
          "staffIds": ["stf_L", "stf_S"] }
      ] }
  ],
  "addons": [ … ]
}

GET /staff

curl -s $BASE/staff -H "Authorization: Bearer $KEY"

Returns { "staff": [ { "id", "name", "title", "skills": [], "bookableOnline": true, "workingDays": ["mon","tue",…] } ] }.

GET /availability

Free start times for one or more services on a date. Multi-service chains are booked back-to-back with one staff member. Uses the public rules (lead time, advance window, online-bookable staff only).

QueryRequiredNotes
dateyesYYYY-MM-DD in the salon timezone
serviceyesone id, or several comma-separated for a chain
addonsnocomma-separated add-on ids (extend duration/price)
staffnorestrict to one staff id
curl -s "$BASE/availability?date=2026-09-10&service=svc_1" -H "Authorization: Bearer $KEY"
{
  "date": "2026-09-10", "timezone": "Asia/Singapore", "durationMin": 90, "priceCents": 10800, "depositCents": 0, "currency": "SGD",
  "reason": null,
  "staff": [ { "id": "stf_L", "name": "Beautician L" }, { "id": "stf_S", "name": "Beautician S" } ],
  "slots": [ { "time": "11:00", "staffIds": ["stf_L", "stf_S"] }, { "time": "11:15", "staffIds": ["stf_L"] } ]
}

reason is set when there are no slots for a structural reason: past, too_far, too_late (inside lead time), not_bookable, no_staff.

GET /availability/days

Which dates in a window have at least one slot — handy for "what's the next day you have?".

curl -s "$BASE/availability/days?from=2026-09-10&days=14&service=svc_1" -H "Authorization: Bearer $KEY"
# → { "from": "2026-09-10", "days": 14, "available": ["2026-09-10", "2026-09-11", …] }

POST /bookings

Create a booking (201). Client is matched by phone (preferred) or email, otherwise created. Requires write.

curl -s -X POST $BASE/bookings -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{
  "date": "2026-09-10", "time": "15:00",
  "serviceIds": ["svc_1"], "addonIds": [], "staffId": null,
  "client": { "firstName": "Aisha", "lastName": "Rahman", "phone": "+65 9123 4567", "email": "aisha@example.com", "waId": "6591234567" },
  "notes": "Prefers a natural cat-eye map",
  "couponCode": "WELCOME10",
  "source": "whatsapp",
  "externalRef": "wa:6591234567:msg_123"
}'

Body fields: date, time, serviceIds[] (required); addonIds[], staffId (null/omitted = any available, load-balanced), client ({id} for an existing client, or {firstName, lastName?, phone, email?, waId?}), notes, couponCode, source (whatsapp | api | ai, default api), externalRef.

{
  "bookings": [ { "id": "bk_1", "code": "GB-7K3M2", "serviceName": "Volume 5-7D Glorious Lashes", "startsAt": "2026-09-10T07:00:00.000Z", "endsAt": "2026-09-10T08:30:00.000Z", "status": "confirmed", "source": "whatsapp", "priceCents": 10800, "discountCents": 1080, "riskScore": 25, "manageUrl": "http://localhost:3000/b/…", "staffId": "stf_L", "clientId": "cl_1" } ],
  "client": { "id": "cl_1", "firstName": "Aisha", "lastName": "Rahman", "phone": "6591234567", "visitCount": 0 },
  "manageUrl": "http://localhost:3000/b/…"
}

Errors: 409 slot_taken (pick another slot — re-query availability), 422 too_late (inside lead time), 422 policy (first-visit-only promo for a returning client, coupon invalid, blocked client), 422 invalid (missing phone when the salon requires it), 404 not_found (service/client id).

GET /bookings

List bookings in a local date range (inclusive, default today → +7 days, max 200).

QueryNotes
from, toYYYY-MM-DD
statuspending confirmed arrived in_progress completed no_show cancelled
clientIdfilter by client
phonefilter by client phone (any format; normalised) — the easy way to answer "what's my booking?"
curl -s "$BASE/bookings?phone=6591234567&from=2026-09-01&to=2026-10-31" -H "Authorization: Bearer $KEY"

Each item is a booking object plus staffName and client.

GET /bookings/:id

curl -s $BASE/bookings/bk_1 -H "Authorization: Bearer $KEY"
# → { "booking": {…}, "group": [ … all services booked together … ], "client": {…}, "staff": { "id", "name" } }

POST /bookings/:id/cancel

Cancels the booking and every service booked with it. The salon's cancellation window (cancelPolicyHours) is enforced and returns 422 policy when too close — pass ?force=1 (needs write) to override, e.g. when the salon itself cancels.

curl -s -X POST "$BASE/bookings/bk_1/cancel" -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{"reason":"Client unwell"}'
curl -s -X POST "$BASE/bookings/bk_1/cancel?force=1" -H "Authorization: Bearer $KEY"

Returns { "bookings": [ …cancelled… ] }. The client is notified through the outbox and the waitlist is offered the freed slot automatically.

POST /bookings/:id/reschedule

curl -s -X POST "$BASE/bookings/bk_1/reschedule" -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{"date":"2026-09-12","time":"16:00"}'

Body: date, time (required), staffId (omit to keep the same staff member; null to allow any). 409 slot_taken when the new time isn't free; 422 policy when inside the cancellation window unless ?force=1. Requires write.

POST /bookings/:id/status

Front-desk status changes (requires write).

curl -s -X POST "$BASE/bookings/bk_1/status" -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{"status":"completed","paidCents":10800,"paymentMethod":"paynow"}'

status: confirmed (approve a pending request) arrived in_progress completed no_show cancelled. With completed you may pass paidCents, paymentMethod, tipCents.

GET /clients

Search. At least one of phone, email, q (name / email / phone fragment). Max 50.

curl -s "$BASE/clients?phone=%2B6591234567" -H "Authorization: Bearer $KEY"

Client objects expose id, firstName, lastName, phone, email, tags, visitCount, noShowCount, lastVisitAt, loyaltyPoints.

POST /clients

Find-or-create by phone/email. 201 when created, 200 when an existing client matched. Requires write.

curl -s -X POST $BASE/clients -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{"firstName":"Aisha","phone":"+65 9123 4567","waId":"6591234567"}'

GET /clients/:id

Client card summary: contact + stats, upcoming bookings, recent completed bookings and pinnedNotes (kinds: allergy, formula, preference, note) so a bot can warn about sensitivities before booking.

curl -s $BASE/clients/cl_1 -H "Authorization: Bearer $KEY"

Webhooks

Configure endpoints in Settings → Integrations → Webhooks. Each delivery is a POST with JSON { "event", "at", "tenantId", "data" } and headers:

Events: booking.created, booking.updated (data.change = rescheduled | new status), booking.cancelled, client.created, review.created, waitlist.offered. Deliveries retry with backoff (up to 6 attempts) until your endpoint returns 2xx. Verify the signature before trusting the payload:

import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Booking object

FieldTypeNotes
id, codestringcode is the short human reference (GB-XXXXX)
groupIdstring · nullshared by services booked together
clientId, staffId, serviceIdstring · null
serviceNamestringdenormalised for history
startsAt, endsAt, createdAtISO UTC
durationMinint
statusenumpending confirmed arrived in_progress completed no_show cancelled
sourceenumonline admin api whatsapp walk_in phone ai
priceCents, discountCents, depositCents, paidCentsintcents
paymentStatusenumunpaid deposit paid refunded waived
addonsarray{id, name, priceCents, durationMin}
clientNotesstring · nullwhat the client typed at booking
riskScoreint · null0-100 no-show risk
manageUrlstringclient self-service link (view / reschedule / cancel / review)

Changelog