Hospitality engine

Add a party waiting now at the door

post/api/waitlist

The source: 'staff' half of the unified Waitlist (migration 0232): a host taking a name and a number at the door, the retired walk-in queue's own job folded into this one table. Any member, not admin-only: taking a name at the door is the job of whoever is on the door.

A phone number is REQUIRED, unlike a source: 'online' entry where an email carries the offer. The entire value here is the guest being somewhere else when the table frees, so an entry that cannot be texted is a person standing at the host stand, which is what this removes. The number is NOT required to be E.164: toE164 resolves whatever the host typed against the company's own country and timezone at send time, so "0412 345 678" works.

requestedDate is always the venue's own local today: there is no date picker at the door, only "now" (book_waitlist_entries_staff_shape).

There is deliberately no public route onto this half. A guest joins by standing in front of a host who reads the room and quotes a wait; a public one would let anyone put any number in any venue's queue from anywhere, and the venue would then text them.

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

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/waitlist" \  -H "Content-Type: application/json" \  -d '{    "name": "string",    "phone": "string",    "party_size": 1  }'
{  "ok": true,  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}

List guests waiting for a table GET

The unified Waitlist (migration 0232): entries with status `open` or `notified`, for today onward, soonest first. The three terminal statuses are deliberately absent; they are history, and this is a list a member acts on during service. `notified` rows are included on purpose rather than filtered out. A member needs to see who has already been offered something, or the second table to come free that evening goes to the same guest. `source` tells `online` (a guest joined THEMSELVES on the booking widget, for a future date that was full) from `staff` (a host added them at the door, right now). `email` is nullable since 0232: a `staff` entry commonly has none.

Offer a waitlisted guest a table POST

The staff-triggered half of the waitlist, in one button. Emails (and texts, if the guest left an E.164 number and the org has SMS on) a link back into the booking flow prefilled with the date and party size, then sets the entry to `notified` with `notifiedAt`. **Send first, mark second**, deliberately. Marking first would be the tidier transaction, but `notified` would then be a lie whenever the mail provider is down: the list would show the guest as offered, nobody would offer them again, and the table would go empty. Failing the other way produces at worst a duplicate "a table has come free" for a table that is in fact still free. Every attempt is recorded in `book_notification_log` against `waitlistEntryId` with type `waitlist_offer`; the third subject 0044 added to that table. There is **no dedupe index** on this subject, so re-offering is legal and expected: the 7pm cancellation the guest did not answer is followed by an 8:30 one an hour later. **v1 is staff-triggered and there is no auto-broadcast.** "First to reply Y wins" needs inbound SMS, which does not exist anywhere in this codebase.