Send a big-group enquiry (public)
/api/book/{slug}/enquiriesWhat a guest sends when their party is over the maxOnlinePartySize a venue has set (migration 0036). Creates a book_group_enquiries row and opens a thread on it, which lands in the dashboard inbox beside its booking conversations.
This is precisely the public unauthenticated write surface migration 0024 declined to build for intake forms; "a standalone shareable form URL would be a new unauthenticated write surface needing its own rate limiting and spam story". It inherits that whole obligation: guestRateLimit on the write budget runs FIRST, before the slug is even resolved, so this cannot be used to probe which slugs exist either.
A party at or below the ceiling is refused, and a venue that has set no ceiling at all refuses everything here. Without that check this endpoint would be an open "email the business" box on every hospitality org.
Path Parameters
The organization's public booking slug, i.e. the {slug} in /{slug}. Only orgs with status active resolve; anything else is a 404.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/api/book/string/enquiries" \ -H "Content-Type: application/json" \ -d '{ "partySize": 1, "name": "string", "email": "string", "phone": "string" }'{ "ok": true, "enquiryId": "aa5368aa-b8dc-438d-a60b-d04164139169"}Preview a promo code before checkout (public, unauthenticated) POST
Never creates or charges anything: a preview so the widget can say "that code works, $12 off" before the guest fills in their details. POST /api/book/{slug}/appointments (the real checkout) independently re-resolves the SAME code via the same resolveBestPromotion() (src/lib/booking/promotions-server.ts) and is the only answer ever actually charged; a "valid" preview here can still be refused at checkout if, for example, the guest turns out to have already used a once-per-customer code (this route has no customer id yet to check that against). Rate-limited on the tighter 'write' guest bucket (30/min): a code is a short, guessable string, and this is the one endpoint that lets someone probe a guess without booking anything.
Join the waitlist (public) POST
What a guest leaves behind when a date came back with **no tables at all** (migration 0044). Creates a `book_waitlist_entries` row with status `open`, which lands on the venue's reservations screen. Until this existed, a fully-booked Friday was a dead end; the widget said "try another day" and the guest left, taking with them the one signal a booking page most wants. **Nothing is held or promised by joining.** A table is offered later by a member pressing a button, and the guest re-books through the ordinary flow; whoever gets there first gets it. Same obligations as every other public write here: `guestRateLimit` on the `write` budget runs FIRST, before the slug is resolved and before the body is parsed, so this cannot be used to probe which slugs exist. A party ABOVE the venue's `maxOnlinePartySize` is refused with `enquiryRequired`; above that line the venue has said it wants to look at the group, and a waitlist entry is a promise that a table might simply be offered.