Shared

Read this business's metered usage for the current billing period (admin)

get/api/organization/usage

What UsageCard renders on /billing. A read of the append-only book_usage_events ledger via lib/usage.ts.

Two metered resources since pass 2 (2026-08-08), and they are not symmetric. sms is gated and re-billed: every sendSms() call site goes through meteredSendSms, and each segment is billed at A$0.09 per text. email is neither: every meteredSendEmail send is counted, but email is included on every plan, is not re-billed, and cannot be capped (see that function's own header). Bookings remain the one documented chokepoint still uninstrumented.

No cap or block lives here or anywhere else (2026-08-07): sms is either off entirely (a plan without texts, or a trialing tier that hasn't converted to paid, per planAllows's own trial gate) or fully on with no allowance and no ceiling. This route only ever reports what was sent, never a reason a send was refused for running out of anything.

allowed: false zeroes usedThisPeriod without reading the ledger for SMS, because there is nothing to meter for a business that cannot use the feature. It no longer short-circuits the whole response: pass 1 did, which would now hide the email count from every company whose plan has no texts, i.e. most of them.

The four top-level fields keep their pass-1 shape and meaning exactly and describe SMS alone; email was added as a nested object rather than a reshuffle, so this stayed an additive change. included is always 0 (no tier or add-on bundles a free allowance any more, see TIER_LIMITS's own comment), kept in the response rather than dropped so UsageCard never has to special-case its absence. One billing-period boundary serves both resources, derived from book_billing.current_period_end via periodStartFor().

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

Response Body

application/json

application/json

application/json

curl -X GET "https://example.com/api/organization/usage"
{  "resource": "sms",  "allowed": true,  "usedThisPeriod": 0,  "included": 0,  "email": {    "usedThisPeriod": 0  }}

Read Gaplessly's own invoices to this org, newest first (admin) GET

Native "Billing history" (migration 0128): what `listBillingInvoices` (`lib/billing/subscription.ts`) reads straight off Stripe's Invoices API for this org's platform-billing customer. Deliberately read-only: there is no write path here that could drift from what Stripe's own list already says, unlike a first-party saved-card manager would. Up to 36 invoices (three years of monthly billing), newest first; an org that has never bought a tier or add-on gets an empty list rather than an error.

Read this org's Stripe Connect status (admin) GET

Whether the org can take guest deposits (migration 0012), in three states rather than two, and the distinction is the point. `connected` means a Connect account exists; `chargesEnabled` means Stripe has actually cleared it to take card payments. **They are not the same, and assuming they are is the failure 0012's own header warns about**: a PaymentIntent is created successfully on a restricted account and only fails when the guest tries to pay it, which would strand a booking half-made. Calls Stripe on every request to re-read the capability status, because it flips when a verification completes; hours or days after the operator left onboarding, and emits no event this app subscribes to. A Stripe outage fails soft: the stored value is returned instead, since a settings page that errors because Stripe is slow is worse than one showing a stale answer. `configured: false` means the deployment has no Stripe keys at all (every preview branch until someone sets them). That is answered 200, not as an error; there is nothing wrong with the request. Reads `book_billing`, which has RLS with no `authenticated` policy, so this goes through the service-role client. There is no tenant-scoped path to these columns by design: it is what stops a member PATCHing themselves a payout destination through PostgREST.