Set the caller's own staff-alert preferences
/api/notification-prefsA login's own choices for which staff-facing booking alerts (a booking is made, cancelled or moved) reach them by email or text (migration 0121's Notifications tab). SELF ONLY, unlike PATCH /api/members/{id} (admin-gated, changes what someone ELSE may do): this changes what reaches the CALLER's own inbox and phone, so it takes no id at all, just requireMember() with no role, scoped to the caller's own book_org_members row by their session user id, never a client-supplied one.
A missing key inside notification_prefs for an event/channel pair means the default (email on, sms off), not "never notify": see src/lib/notifications/staff-prefs.ts. Whole-object replacement, not a merge; send the full set every time.
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 PATCH "https://example.com/api/notification-prefs" \ -H "Content-Type: application/json" \ -d '{ "notification_prefs": { "property1": {}, "property2": {} } }'{ "ok": true}Invite a teammate (admin) POST
Three privileged calls in a fixed order: send the invite, write the `company_id` claim, insert the roster row. The claim is set BEFORE the invitee ever signs in, so their very first JWT already carries it. Any failure after the invite deletes the auth user so the invite can simply be retried. An email that already has an account anywhere in the platform is refused; moving accounts between organizations is not supported.
Toggle a Recipe on/off, or edit its message (admin) PATCH
Org-wide marketing configuration for one Recipe (docs/competitive-gap-analysis.md:1260-1424): ADMIN-gated, unlike PATCH /api/notification-prefs' self-only shape, since this changes what every customer of the business receives, not one login's own alert preferences. Writes book_companies.recipe_prefs past its SELECT-only grant (migration 0156) via service_role. `state` accepts only `off`/`automatic` today. `ask_first` is a valid value already at rest in storage (recipe_prefs is free text) but has no landing surface yet (no "Today" page), so this route 400s it rather than silently accepting a setting nothing acts on. `subject`/`body` are optional and independent of `state`: sending either (or both) sets a per-org override for that recipe's message, read by recipeTemplate() (src/lib/marketing/recipes/definitions.ts) ahead of the shipped default; an explicit `null` on either resets it back to the default.