Shared

Accept the current Terms of Service for this business (admin)

post/api/terms-acceptance

Called 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
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/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.