Docs
Billing
One buyer: the organization. It holds a ceiling that everyone inside it draws from — people and agents alike — and its administrators may cap any one of them. Limits are enforced server-side at write time; over-limit returns 402 Payment Required with who hit what and who can lift it.
Plans and the four meters
Plans are a catalog the operator manages from the admin panel (/admin/plans, the planstable): each plan has a name, a price, either monthly ceilings per meter or a one-off credit in dollars, and flags for whether it is visible and whether it can still be assigned. A plan can be made for exactly one organization, and a retired plan keeps working for whoever holds it. The organization's plan is organizations.plan. The contract keeps only the vocabulary — the meters and the shape of a refusal — because that is product; the numbers are not in the repository.
monthly — per-meter ceilings that reset each UTC month
one_off — a credit in dollars, spent when it is spent; the credit is the only wallEach meter is either real money leaving (models, transcription) or a real promise kept (how much context we hold, how much of it we serve). ai_queries, mcp_calls and meeting_minutes are flows, counted per UTC month in org_usage_counters per organization and per member. nodes is a stock — it is counted from the rows themselves, never accumulated, which is why it has no period. A plan that sets no ceiling for a meter has no ceiling there.
mcp_calls is what an organization's agents read. Nothing recorded reads until it was metered explicitly: the activity log has only ever held writes, so a month of retrieval left no trace anywhere.
A workspace has no plan
There is one subscription level and the organization holds it. What a workspace may hold is derived — workspaceCapacity(orgPlan, quota) in org-plans.ts— and never stored: the organization's node ceiling, its meeting hours, and the plan's per-workspace agent cap.
An administrator who wants one project fenced off assigns it a quota (workspaces.custom_node_limit, custom_meeting_hours), which can only narrow what the plan grants — widening it would be a way to buy capacity without paying for it. Two levels of subscription was a model bug: it made “what is this company paying” unanswerable and let one customer sit on two disagreeing plans at once.
Capping one person inside the organization
With nothing configured, everybody draws on the organization's ceiling — which is what a team of five wants. An owner or admin may set a monthly number for any one member in organization_member_limits, which in practice is aimed less at people than at an agent consuming what the rest of the company needs.
Enforcement asks the member's own limit first, then the organization's: the two answers need different people to resolve them.
Enforcement
Mutating routes guard their create path with checkUsage from src/lib/organizations/limits.ts, and record what they spent with recordUsage.
{
"error": "limit_reached",
"kind": "ai_queries",
"scope": "organization",
"limit": 2000,
"current": 2000,
"plan": "team",
"admin_can_raise": false,
"message": "Your organization has used 2000 of 2000 model calls this month on the Team plan. Raising this means changing plan."
}Stripe
POST /api/billing/checkout sells an organization plan — { kind: "organization", organization_id, plan, cadence } — and POST /api/billing/portal — { kind: "organization", organization_id } — manages it. Both need an owner or admin of the organization. kind: "workspace" is refused with a 400: a workspace never had a subscription to open a portal on. Only a plan with a Stripe price is checkoutable; a custom plan is assigned by the operator from the admin panel once the pricing conversation has actually happened.
Subscriptions bought under the old per-workspace price ids are still live and still recognised by the webhook: they bought capacity, and capacity is now something the organization holds.