API · Endpoints

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

StatusBodyDescription
200objectBilling 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

FieldTypeRequiredDescription
plan"pro" | "scale"noTarget plan. Defaults to `pro`.

Responses

StatusBodyDescription
200objectCheckout URL.
400ErrorStripe not configured on this deployment.
409ErrorSubscription 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

StatusBodyDescription
200objectPortal URL.
400ErrorStripe 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

FieldTypeRequiredDescription
plan"pro" | "scale"noTarget plan. Defaults to `pro`.
confirm_tokenstringnoRequired for AGENT-initiated live plan flips. Returned on the first call.
mode"chat" | "web"noAgent-only consent UX. `chat` (default) = handshake via confirm_token. `web` = mint a web-approve URL the user clicks.

Responses

StatusBodyDescription
200objectDiscriminated by `status`: `switched` | `resumed` | `checkout-required` | `confirmation_required` | `web-approve-required` | `design-partner`.
400ErrorStripe 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

FieldTypeRequiredDescription
confirm_tokenstringno
mode"chat" | "web"no

Responses

StatusBodyDescription
200objectDiscriminated by `status`: `downgrade-scheduled` | `already-free` | `confirmation_required` | `web-approve-required`.
400ErrorInvalid 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

FieldTypeRequiredDescription
kind"agents" | "workspaces" | "rows" | "other"noDefaults to `other`.
desiredValueintegernoWhat the caller would like the cap to be.
reasonstringnoFree-text context. Truncated to 500 chars.

Responses

StatusBodyDescription
200objectLogged.

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.