Shared

Publish the Booking Page settings draft live (admin)

post/api/booking-page-draft/publish

Copies draft_content onto the real book_companies columns it mirrors and stamps booking_page_published_at. Unconditionally admin-only, with no manage_org_settings escape hatch, the same posture POST /api/site-pages/{type}/publish takes and for the identical reason: a staff caller who can edit a draft must still not be able to make it live.

No re-validation here on purpose, since PATCH /api/booking-page-draft already validated every value on the way in, so this route trusts what it reads back out. A company that never touched this screen since the draft system shipped gets a clean 200 no-op: book_companies already holds whatever was last saved the old, still-live instant-save way, and there is nothing to copy forward.

Authorization

sessionCookie
sb-qxrvgfkjyvbngipqvslu-auth-token<token>

The dashboard's Supabase Auth session cookie, set at sign-in. Large sessions are split across numbered chunks (…auth-token.0, .1), so treat this as a cookie family rather than one name.

Every request re-validates it against the Auth server (getUser()), never by decoding the cookie locally: a JWT nothing has checked is not a credential. Tenancy is then read from the verified app_metadata.company_id claim and enforced by row-level security; it is never read from request input, on any route, ever.

role (admin / staff) is deliberately not in RLS. It gates specific actions in route code, the operations marked admin below, so hiding a button in the UI is cosmetic only, and a route's own check is the enforcement.

In: cookie

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/booking-page-draft/publish"
{  "ok": true}

Save the Booking Page settings draft (admin, or manage_org_settings) PATCH

Writes `draft_content` ONLY (`book_booking_page_drafts`, migration 20260911151213); `book_companies`' own accent_color/tagline/booking_theme/etc. columns, which the public widget, emails, wallet passes and marketing campaigns all read as the live published truth, never move until POST .../publish. Same admin-or-manage_org_settings gate as PATCH /api/companies, since this is the exact same bucket of settings, just landing in a different column for now. Accepts a PARTIAL patch: only the keys present in the body are validated and merged onto whatever draft already exists (BOOKING_PAGE_DRAFT_FIELDS, src/lib/booking/booking-page-draft-input.ts). An unknown key, or any field failing its own validation, rejects the whole patch with a 400 naming the first problem found. Both verticals: the Booking Page settings screen applies to every company regardless of business_type, so this route names no engine.

Dates that do not follow the recurring week GET

Special hours (`book_special_hours`, migration 0109): what a business does on ONE date, instead of the weekly hours or service periods that otherwise repeat forever. **Both engines**, unlike `/api/organization/hours`. "Closed on Christmas Day" and "open 10:00 to 14:00 on Boxing Day" are sentences a salon and a restaurant both need, and each engine composes them with its own maths: for appointments the windows replace `book_company_hours` for that date, for hospitality they clamp that date's `book_service_periods` (which keep their own name, turn time and covers cap) and extend them where the windows reach past anything the venue has defined. Returns dates from the business's own today onward, in date order, capped at 400. `from` overrides the start. `today` comes back alongside, computed in `book_companies.timezone` rather than the server's, so a caller does not have to guess which day the venue is on. Readable by any member; only the writes are gated.