Save Gaplessly's own billing details for this org (admin)
/api/organization/billingOnly 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.
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/organization/billing" \ -H "Content-Type: application/json" \ -d '{}'{ "ok": true}Read Gaplessly's own billing details for this org (admin) GET
Migration 0128, the scoped-down native-billing build (docs/backlog.md): a SECOND, distinct ABN and a receipts-email override for GAPLESSLY'S OWN invoices to this venue, kept on `book_billing` rather than `book_companies`. Not the same field as `receiptAbn` on `PATCH /api/companies` (migration 0126), which is the venue's ABN on ITS OWN guest receipts; the two answer opposite questions and live on opposite tables on purpose. Reads `book_billing`, which has RLS with no `authenticated` policy, so this goes through the service-role client, the same reason `/api/organization/addons` and `/api/organization/tier` do. Both fields default to null when the org has never set them, or has no `book_billing` row at all yet.
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.