Guest self-service

Submit the private half of a booking's post-visit feedback

post/api/feedback/{token}

The other branch of the post-visit satisfaction gate a completed booking is emailed (see notifyBookingCompleted/notifyReservationCompleted): a guest who says a visit was NOT great lands here instead of the org's public Google review link, so the negative signal still reaches the business without ever becoming a public review.

No slug, unlike the manage-link endpoint; the manage_token is unique across both engines by construction and is the whole credential, so it resolves a booking on its own. Guest name/email are read from the booking's own customer record rather than trusted from the request body, since the token already proves who this is.

PUT-like replace semantics on a POST: a guest reopening the link edits their existing answer rather than stacking a second row, the same "one response per booking" convenience the intake form gives.

Path Parameters

token*string

The booking's manage token (a uuid), reused as the feedback link's credential too; see feedbackUrl() in lib/booking/manage.ts.

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/feedback/string" \  -H "Content-Type: application/json" \  -d '{}'
{  "ok": true}

Answer the venue's intake form PUT

The guest fills in whatever questions the org defined for this booking (migration 0024). Serves both engines for the same reason PATCH does; the token is a link to *a booking*, and which engine that booking belongs to is resolved from the org's `business_type` inside `loadByToken`. PUT rather than POST because there is exactly one response per booking, enforced by a partial unique index per subject id: a guest reopening their link EDITS their answers rather than stacking a second submission staff would have to reconcile. The form is re-resolved server-side rather than taken from the body: the page may have been rendered days ago, and the org may have switched forms or deactivated one since. A service-specific form wins over the org-wide one, and only `active` forms are served. Answers are validated against that form; required questions present, dropdown answers within their `options`, lengths capped, and `fields_snapshot` is rewritten from it on every save, so staff always read these answers against the questions that were actually on screen. Deliberately NOT gated on the org's guest-self-service policy, unlike PATCH and DELETE, and for the same reason POST is not: an org that switched off guest rescheduling still asked these questions, and a notice period is backwards here; the hour before a visit is exactly when someone remembers their allergy.

Set or clear a customer's marketing consent from the link in a marketing email POST

The write half of the unsubscribe surface (migration 0143); the page at /unsubscribe/{token} only renders, this route is the only thing that mutates. POST only, form-encoded, never JSON: read via `request.formData()`, which parses both multipart/form-data and application/x-www-form-urlencoded transparently. That matters for RFC 8058: a mail client rendering a native "Unsubscribe" button POSTs exactly `List-Unsubscribe=One-Click` as application/x-www-form-urlencoded, and this route reads it with the same parser the guest-facing form on the page uses, so there is one code path for both a mail client's automated click and a guest's real one. `token` is `book_customers.unsubscribe_token`, a per-customer-per-company credential (not global): a person who books at two venues has two separate tokens and unsubscribing at one never touches the other. No session; the token alone resolves who this is, same posture as /api/feedback/{token}. An unrecognised or empty body defaults to `unsubscribe`, never `resubscribe`: fail-safe toward less contact, not more. A `stopSms=1` field alongside an unsubscribe additionally sets `sms_opt_out_at`; it has no effect on a resubscribe, since SMS consent is its own axis and a resubscribe must never silently reinstate it.