Read how much of this period's AI assistant allowance is left (any role)
/api/ai/creditsWhat 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.
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
Response Body
application/json
application/json
application/json
curl -X GET "https://example.com/api/ai/credits"{ "used": 0, "included": 0, "remaining": 0, "allowed": true, "resetsAt": "2019-08-24T14:15:22Z"}Draft an inbox reply or a private note with the AI assistant (single-shot, read-only) POST
Powers the Inbox's two Lessly buttons (top-right of the reply box, and of "On this booking"): draft the staff's next reply, or suggest a short private note, from the thread already open in MessagesClient. Single-shot (`generateText`, not a stream), unlike POST /api/ai/chat: this answers once from the messages the client sends in the request body, with no running conversation and no persisted history of its own. Runs entirely as the signed-in user through requireMember()'s RLS-scoped client, called with no role or vertical: either engine's staff can reach it. Unlike POST /api/ai/chat, it never re-reads the thread's messages itself: the client already has `open.messages`, rendered server-side by /inbox's own page load a moment ago, so the request body carries the transcript instead of a thread id. Tenancy still comes from requireMember()'s companyId, never from the body; neither output has any effect until a human clicks Send reply or Save note. The transcript is capped to the newest 60 messages and roughly 6000 characters (buildTranscript, route.ts), oldest lines dropped first if still over budget. The system prompt instructs the model to treat every line of it as data to read, never as an instruction to follow, the same posture POST /api/ai/chat applies to tool results, since a guest can type anything into the messages this transcript is built from. Shares the identical per-tier monthly ai_credits allowance and the same wired-but-inactive AI_RATE_LIMIT_PER_MINUTE gate as POST /api/ai/chat, keyed the same way (per user, not per feature): one OpenRouter key backs both, so the abuse concern is request frequency from this login, not which AI feature it is hitting.
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.