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.

Frequently asked questions

How do I read my Dock org's billing summary via the API?
GET `/api/billing`. Returns plan, monthly price in cents, active vs cap counts for every gated resource (agents, members, workspaces, rows, API calls, webhooks), card on file, next invoice date.
How do I upgrade my Dock plan from an agent via the API?
POST `/api/billing/upgrade` with `{ plan: 'pro' | 'scale' }`. Three response paths: Free (no card) → `{ status: 'checkout-required', checkoutUrl }`; same plan → `{ status: 'resumed' }`; switching paid plans → consent-gated via `confirm_token` or `approval_url`.
How do I downgrade my Dock plan via the API?
POST `/api/billing/downgrade`. Schedules a downgrade to Free at end of current period. Your current plan + caps stay live until period-end. Consent-gated via the same two-call dangerous-ops handshake.
How do I open the Stripe customer portal for a Dock org?
POST `/api/billing/portal`. Returns `{ url }` to a Stripe-hosted billing portal session (expires in minutes). Hand the URL to the human; they manage card + invoices + manual cancel through Stripe directly.
How does my Dock agent request a higher plan limit via the API?
POST `/api/billing/request-limit-increase` with `{ kind: 'agents' | 'workspaces' | 'rows' | 'other', desiredValue?, reason? }`. Registers the signal on the admin side; we bump the cap when the pattern matches. No reply loop; no sales call.
What's the response shape for hitting a Dock plan cap via the API?
402 Payment Required with `{ error, message, details: { plan, active, cap, upgrade: { mcpTool, api, plan }, increase: { mcpTool, api } } }`. Agents read `details.upgrade` and `details.increase` as the next-step recommendation.
How do I confirm a Dock plan upgrade my agent initiated?
Two surfaces. Chat mode: agent shows the `message` from the first POST, you say yes, agent re-POSTs with `confirm_token`. Web mode: agent prints `approval_url`, you click + approve in browser, agent polls `polling_url`.
Can I see my Dock invoice history via the API?
Programmatic invoice listing is on the roadmap; today, use `POST /api/billing/portal` to get a Stripe portal URL where humans can view + download every invoice. The portal session is short-lived but covers full historical access.
Does Dock charge per agent or per seat?
Neither. Flat monthly per org: Free $0, Pro $19, Scale $49. Spawn as many agents as you want inside your plan cap and the bill stays the same. Plan caps cover agents, members, workspaces, rows, API calls, webhooks. Learn more →
How do I cancel my Dock subscription?
From the dashboard: Settings → Billing → Cancel. Or POST `/api/billing/downgrade` schedules a drop to Free at period-end (effectively a cancel for the paid features). Stripe portal (`/api/billing/portal`) also offers cancel.
Updated