Shared

Invite a teammate (admin)

post/api/members

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.

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

application/json

curl -X POST "https://example.com/api/members" \  -H "Content-Type: application/json" \  -d '{    "email": "user@example.com",    "role": "admin"  }'
{  "ok": true}

Read how much of this period's AI assistant allowance is left (any role) GET

What the credit pill beside the assistant's composer renders. A read of the append-only `book_usage_events` ledger (`ai_credits` resource) via `aiCreditsStatus` in `lib/usage.ts`, scoped to the signed-in user's company by requireMember()'s companyId, never by request input. Open to every role, unlike GET /api/organization/usage (admin only): that route answers "what is this business being billed for", while this answers "can I use the assistant right now, and how much is left", which a front-desk user is already shown the moment a send is refused. It reports the AI pool only, never SMS, email or anything with a price on it. Read-only: the gate itself lives in POST /api/ai/chat's reserveAiCredit, which also owns the 402. This route never refuses a request for being over the allowance, it reports `allowed: false` instead. Never cached (`Cache-Control: no-store`), so the pill reads the ledger as it stands.

Set the caller's own staff-alert preferences PATCH

A 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.