Accept the current Terms of Service for this business (admin)
/api/terms-acceptanceCalled by the dashboard's "We've updated our terms" prompt (TermsUpdatePrompt.tsx), which an admin sees when their business has no acceptance of the current TERMS_VERSION (legal.ts) on record. Records { company_id, user_id, version, accepted_at } in book_terms_acceptances (migration 20261001120000), a service-role-only table, so no tenant session can forge or erase an acceptance through PostgREST. One row per business per version: a second admin accepting the same version is a no-op.
The body names the version the admin was shown. If TERMS_VERSION moved between the prompt rendering and the click, the route answers 409 instead of recording acceptance of a text they never saw.
Authorization
sessionCookie 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/terms-acceptance" \ -H "Content-Type: application/json" \ -d '{ "version": "string" }'{ "ok": true, "version": "string"}Attest or withdraw the pre-existing-list marketing declaration (admin, or manage_org_settings) PATCH
Writes book_companies' marketing_attested_at/marketing_attested_by/marketing_attested_text (migration 0143). Deliberately its own route, not folded into PATCH /api/companies or PATCH /api/booking-policy: these three columns carry NO grant to `authenticated` at all, being a legal declaration about permission to contact other people, so a column grant would let any staff login mint or withdraw it via raw PostgREST (0143's own comment on that column group). Written with service-role, past this route's own admin gate, the same posture booking-policy.ts already uses for its own ungranted columns. `{ action: "attest" }` sets `marketing_attested_at` to now, `marketing_attested_by` to the caller's user id, and snapshots `MARKETING_ATTESTATION_TEXT` (consent.ts) into `marketing_attested_text` verbatim, so the string an admin agreed to and the string kept on file are the same value by construction. `{ action: "withdraw" }` clears `marketing_attested_at` back to null and leaves `marketing_attested_by`/`marketing_attested_text` alone as a record of the last declaration made: the audience rule (`canReceiveMarketing`, consent.ts) is time-scoped by `attested_at` alone, so this one flip instantly narrows the emailable audience back to explicit opt-ins for every customer at once, with nothing to backfill on the book_customers side. The widget-visibility toggle (`booking_marketing_optin_enabled`) is a SEPARATE column with its own grant to `authenticated`, and goes through the ordinary PATCH /api/companies save instead, since it is a display setting, not itself a consent record, so it does not belong on this route.
Turn review routing on or off for this business (admin) POST
Called from Reviews > Google review link ("Who gets your Google link"). Off, the default, a guest who taps thumbs down or rates below the star threshold lands on the private feedback form with a link to Google underneath, so every guest is offered the public option. On, that guest sees only the private form. Writes `book_companies.review_routing_enabled` with the service role: the column has no update grant to `authenticated` (migration 20261002100000), so this route is the only way to switch routing on. Switching on requires `acknowledged: true` and the current notice `version` (`REVIEW_ROUTING_ACK_VERSION`, lib/feedback/review-routing.ts), and writes a `review_routing_enabled` row to `book_compliance_events` (append-only, service-role only) BEFORE the setting changes, recording who agreed and the exact notice text shown. Switching off needs only `enabled: false`; its `review_routing_disabled` row is written best-effort, after the setting changes.