Billing
Read the org's plan + status, open a Stripe Customer Portal, switch tiers, or register a limit-increase request. Plan changes initiated by AGENT principals require a two-call consent handshake; HUMAN principals execute directly.
Generated from src/lib/api-paths/billing.ts. Billing is org-scoped via the caller's principal — there's no {slug} in the path.
get/api/billing
Get billing summary
Current plan, subscription status, agent/member/workspace counts, next-invoice date, card on file. Both users and agents can call.
Auth: Bearer token (API key or OAuth access token).
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Billing summary. |
post/api/billing/checkout
Create a Stripe Checkout session
Low-level — returns a Checkout URL for entering a card. Prefer `POST /api/billing/upgrade` in most flows; that handles the plan-switch branches too. Use this when you specifically want a fresh Checkout (e.g. after a portal cancellation). 409s if a subscription already exists.
Auth: Bearer token (API key or OAuth access token).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
plan | "pro" | "scale" | no | Target plan. Defaults to `pro`. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Checkout URL. |
400 | Error | Stripe not configured on this deployment. |
409 | Error | Subscription already exists, or org is on design-partner Scale at $0. |
post/api/billing/portal
Create a Stripe Customer Portal session
Returns a short-lived URL for the Stripe Customer Portal — card changes, invoice history, manual cancellation. The portal itself is browser-only; agents can hand the link back to their user.
Auth: Bearer token (API key or OAuth access token).
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Portal URL. |
400 | Error | Stripe not configured. |
post/api/billing/upgrade
Upgrade plan (Pro or Scale)
Upgrade or switch to a paid plan. **Behavior depends on principal type:** - **HUMAN** (session cookie): one-call execution. The Settings UI click is itself the consent signal. - **AGENT** (DK key, OAuth bearer): consent-gated. First call returns `{ status: "confirmation_required", confirm_token, message, expires_in }`. Agent surfaces the message to its user, user confirms, agent re-calls within 60s with the `confirm_token` set. Optionally pass `mode: "web"` for a web-approve URL the agent prints in chat — agent then polls `/api/web-approve/status`. **Fast paths** (both principal types): no existing subscription returns a Checkout URL; same-plan + scheduled-cancel resumes; design-partner orgs no-op at Scale.
Auth: Bearer token (API key or OAuth access token).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
plan | "pro" | "scale" | no | Target plan. Defaults to `pro`. |
confirm_token | string | no | Required for AGENT-initiated live plan flips. Returned on the first call. |
mode | "chat" | "web" | no | Agent-only consent UX. `chat` (default) = handshake via confirm_token. `web` = mint a web-approve URL the user clicks. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Discriminated by `status`: `switched` | `resumed` | `checkout-required` | `confirmation_required` | `web-approve-required` | `design-partner`. |
400 | Error | Stripe not configured, or invalid confirm_token. |
post/api/billing/downgrade
Schedule a downgrade to Free
Schedule a downgrade to Free at the end of the current billing period. The org keeps its current plan + caps until then. Idempotent on already-Free orgs. Same human-vs-agent consent split as `/api/billing/upgrade`: humans execute directly; agents must use the `confirm_token` handshake (or `mode: "web"` for the web-approve flow).
Auth: Bearer token (API key or OAuth access token).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
confirm_token | string | no | |
mode | "chat" | "web" | no |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Discriminated by `status`: `downgrade-scheduled` | `already-free` | `confirmation_required` | `web-approve-required`. |
400 | Error | Invalid confirm_token. |
post/api/billing/request-limit-increase
Request a limit increase
Agentic escape hatch. When the org bumps a cap (agents, workspaces, rows, anything), call this to register demand. We count signals on the admin side; no ticket, no reply loop, no email. Body is optional — at minimum we record "someone hit a cap".
Auth: Bearer token (API key or OAuth access token).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
kind | "agents" | "workspaces" | "rows" | "other" | no | Defaults to `other`. |
desiredValue | integer | no | What the caller would like the cap to be. |
reason | string | no | Free-text context. Truncated to 500 chars. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Logged. |
Dangerous-ops handshake
Plan flips are gated through src/lib/billing-consent.ts. When an agent posts to /api/billing/upgrade or /api/billing/downgrade without a confirm_token, the endpoint returns a 200 with { status: "confirmation_required", confirm_token, message, expires_in }. The agent surfaces the message to its user, the user confirms, and the agent re-calls with the same token within 60 seconds.
Alternative: pass mode: "web" instead. The endpoint returns a one-click web-approve URL the agent can print in chat; the agent then polls /api/web-approve/status for the result.
Related
- Plans + caps — Free / Pro / Scale.
- Dangerous-ops handshake — full pattern + guarantees.