Read Gaplessly's own invoices to this org, newest first (admin)
/api/organization/billing/invoicesNative "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.
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
application/json
curl -X GET "https://example.com/api/organization/billing/invoices"{ "invoices": [ { "id": "string", "number": "string", "createdAt": "2019-08-24T14:15:22Z", "amountCents": 0, "currency": "string", "status": "draft", "hostedInvoiceUrl": "string", "invoicePdf": "string" } ]}Save Gaplessly's own billing details for this org (admin) PATCH
Only the keys actually sent are touched: either field, or both, in one call. `abn` is normalised the same way `receiptAbn` is on `PATCH /api/companies` (`normalizeAbn`, `lib/billing/receipt-format.ts`): spaces and hyphens stripped, then must be exactly 11 digits or empty. `receiptsEmail` follows the same email-shape check every other admin-settings route in this file uses. An empty string on either field CLEARS it to null rather than being ignored. Upserts into `book_billing` without ever naming `tier`/`subscription_status`/`stripe_customer_id` in the payload, the same trap `ensureCustomer`'s own comment (`lib/billing/subscription.ts`) names for every other upsert into this table: naming those columns in an upsert would reset a paying org. Admin-only: this is a fact about what the organization is billed, the same class of act as a tier or add-on change.
Read this business's metered usage for the current billing period (admin) GET
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()`.