Resource guide
Billing belongs to the payer
Workspace usage can be visible to members, while payment methods, automatic top-ups, and personal limits and the subscription belong to the designated billing user. A workspace owner may designate another owner as the payer; that does not let the first owner manage the second person's billing.
The payer must remain a live workspace owner. Reassignment, role demotion, and removal are checked inside the same durable mutation, so concurrent administration cannot leave a workspace with an ineligible payer. The audit log records each sensitive transition.
{
"autoTopupEnabled": true,
"autoTopupThresholdMicroUsd": 5000000,
"autoTopupAmountMicroUsd": 20000000,
"maxMonthlySpendMicroUsd": 100000000
}Keep units explicit
Contract amounts use integer micro-US dollars. Convert only at the display boundary and keep the integer value through calculations so rounding cannot create or erase spend.
Ledger metadata is an internal finite-JSON audit context capped at 16 KiB before storage. Public history deliberately projects only the billing facts it names; it never truncates metadata while paging or returns the private context.
Handle payment blocks as ownership flows
A payment-required response should send the responsible payer to billing settings. Retrying with a different key does not repair an inactive subscription, depleted balance, or monthly cap.
Keep card data on Stripe
Duet creates Stripe-hosted Checkout and Customer Portal sessions. Card entry and payment-method management therefore stay on Stripe's origin; Duet stores only the customer reference needed for webhooks and a readiness timestamp written only after Stripe verifies a successful setup or payment. A canceled Checkout never enables the portal or automatic top-ups. Successful top-ups still become credits through the idempotent webhook-to-ledger path.
Subscription Checkout accepts a catalog plan name, never a Stripe price reference. Duet resolves the environment-owned price at the server boundary. Upgrades invoice immediately, while downgrades and cancellation take effect at the paid-through boundary. In every case, a verified paid invoice is the only authority that grants a new monthly credit bucket; requesting a plan change never writes credits directly.
The Customer Portal is intentionally narrower than the first-party subscription controls. It owns payment-method updates and invoice history, while Duet owns plan changes so every tier follows the same payer authorization and catalog-price rules.
The /v1/user/billing/* routes accept the same human identity used by the CLI and the dashboard.
They act on that person's payer account; holding a workspace owner's role does not let you manage
another person's billing. App and OAuth credentials do not become personal account access.
Billing
11 operations /v1 /ws /{workspaceSlug} /billing-userDesignate a current live owner as the workspace payer.
- Scope
ws:{workspaceSlug}:admin- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "userId": { "type": "string", "minLength": 1 } }, "required": [ "userId" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "billingUserId": { "type": "string" }, "member": { "type": "object", "properties": { "id": { "type": "string" }, "email": { "type": "string", "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" }, "name": { "type": "string" }, "role": { "type": "string", "enum": [ "owner", "editor", "viewer" ] }, "isBillingUser": { "type": "boolean" } }, "required": [ "id", "email", "role", "isBillingUser" ], "additionalProperties": false } }, "required": [ "billingUserId", "member" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /ws /{workspaceSlug} /billing-capSet or clear the workspace monthly spend cap.
- Scope
ws:{workspaceSlug}:admin- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "monthlyCapMicroUsd": { "anyOf": [ { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, { "type": "null" } ] } }, "required": [ "monthlyCapMicroUsd" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "monthlyCapMicroUsd": { "anyOf": [ { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, { "type": "null" } ] } }, "required": [ "monthlyCapMicroUsd" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /ws /{workspaceSlug} /billing /usageRead workspace usage grouped over a reporting period.
- Scope
workspace_member- Request
- No JSON request body
- Query
Query parameters JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "period": { "default": "monthly", "type": "string", "enum": [ "daily", "weekly", "monthly" ] }, "group": { "default": "workspace", "type": "string", "enum": [ "workspace", "user", "app" ] }, "start": { "type": "string" }, "end": { "type": "string" } } }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "period": { "type": "string", "enum": [ "daily", "weekly", "monthly" ] }, "group": { "type": "string", "enum": [ "workspace", "user", "app" ] }, "rows": { "type": "array", "items": { "type": "object", "properties": { "workspaceId": { "type": "string" }, "period": { "type": "string" }, "spendMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "userId": { "type": "string" }, "appId": { "type": "string" }, "appSlug": { "type": "string" }, "inputTokens": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "outputTokens": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 } }, "required": [ "workspaceId", "period", "spendMicroUsd" ], "additionalProperties": false } } }, "required": [ "period", "group", "rows" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /ws /{workspaceSlug} /billing /ledgerList the workspace credit ledger newest-first with actor attribution.
- Scope
workspace_member- Request
- No JSON request body
- Query
Query parameters JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "cursor": { "type": "string", "minLength": 1 }, "limit": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 } }, "additionalProperties": false }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "amountMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "type": { "type": "string", "enum": [ "grant", "topup", "usage", "adjustment", "reservation", "subscription_grant", "subscription_expiry" ] }, "actorUserId": { "type": "string" }, "tier": { "type": "string", "enum": [ "frontier", "balanced", "openai-frontier", "openai-max", "anthropic-frontier", "anthropic-max", "economy", "services" ] }, "createdAt": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "id", "amountMicroUsd", "type", "createdAt" ], "additionalProperties": false } }, "nextCursor": { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "null" } ] } }, "required": [ "items", "nextCursor" ], "additionalProperties": false }- Errors
Opaque collection cursor is malformed or bound to another walk (400)
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "error": { "type": "string", "const": "invalid_cursor" } }, "required": [ "error" ], "additionalProperties": false, "description": "Opaque collection cursor is malformed or bound to another walk (400)" }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billingRead the payer account and every workspace it funds. Answers a user API key as well as the dashboard.
- Scope
human- Request
- No JSON request body
- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "autoTopupEnabled": { "type": "boolean" }, "autoTopupThresholdMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "autoTopupAmountMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "maxMonthlySpendMicroUsd": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "monthlySpendMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "monthlySpendPeriodKey": { "type": "string" }, "balanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "lastTopupError": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "at": { "type": "number" } }, "required": [ "code", "message", "at" ], "additionalProperties": false }, "consecutiveTopupFailures": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "firstPaidAt": { "type": "number" }, "paidCreditBalanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "monthlyCreditBalanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "subscription": { "anyOf": [ { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "status": { "type": "string", "enum": [ "incomplete", "incomplete_expired", "trialing", "active", "past_due", "canceled", "unpaid", "paused" ] }, "isActive": { "type": "boolean" }, "currentPeriodStart": { "type": "number" }, "currentPeriodEnd": { "type": "number" }, "creditsExpireAt": { "type": "number" }, "cancelAtPeriodEnd": { "type": "boolean" }, "pendingPlan": { "type": "string", "enum": [ "starter", "pro", "max" ] } }, "required": [ "plan", "status", "isActive", "currentPeriodStart", "currentPeriodEnd", "creditsExpireAt", "cancelAtPeriodEnd" ], "additionalProperties": false }, { "type": "null" } ] }, "trial": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "available", "exhausted", "ended" ] } }, "required": [ "status" ], "additionalProperties": false }, "hasPaymentMethod": { "type": "boolean" }, "workspaces": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "slug": { "type": "string" }, "name": { "type": "string" }, "storage": { "type": "object", "properties": { "usableGb": { "type": "number" }, "usedBytes": { "type": "number" }, "sampledAt": { "type": "number" }, "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "nextPlan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "nextUsableGb": { "type": "number" } }, "required": [ "usableGb", "plan" ], "additionalProperties": false } }, "required": [ "id", "slug", "name", "storage" ], "additionalProperties": false } } }, "required": [ "autoTopupThresholdMicroUsd", "monthlySpendMicroUsd", "balanceMicroUsd", "consecutiveTopupFailures", "paidCreditBalanceMicroUsd", "monthlyCreditBalanceMicroUsd", "subscription", "trial", "hasPaymentMethod", "workspaces" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billingUpdate the signed-in payer top-up and account-limit settings.
- Scope
human- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "autoTopupEnabled": { "type": "boolean" }, "autoTopupThresholdMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "autoTopupAmountMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "workspaceSlug": { "type": "string" }, "maxMonthlySpendMicroUsd": { "anyOf": [ { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, { "type": "null" } ] } }, "required": [ "workspaceSlug" ], "additionalProperties": false }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "autoTopupEnabled": { "type": "boolean" }, "autoTopupThresholdMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "autoTopupAmountMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 }, "maxMonthlySpendMicroUsd": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "monthlySpendMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "monthlySpendPeriodKey": { "type": "string" }, "balanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "lastTopupError": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "at": { "type": "number" } }, "required": [ "code", "message", "at" ], "additionalProperties": false }, "consecutiveTopupFailures": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "firstPaidAt": { "type": "number" }, "paidCreditBalanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "monthlyCreditBalanceMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "subscription": { "anyOf": [ { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "status": { "type": "string", "enum": [ "incomplete", "incomplete_expired", "trialing", "active", "past_due", "canceled", "unpaid", "paused" ] }, "isActive": { "type": "boolean" }, "currentPeriodStart": { "type": "number" }, "currentPeriodEnd": { "type": "number" }, "creditsExpireAt": { "type": "number" }, "cancelAtPeriodEnd": { "type": "boolean" }, "pendingPlan": { "type": "string", "enum": [ "starter", "pro", "max" ] } }, "required": [ "plan", "status", "isActive", "currentPeriodStart", "currentPeriodEnd", "creditsExpireAt", "cancelAtPeriodEnd" ], "additionalProperties": false }, { "type": "null" } ] }, "trial": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "available", "exhausted", "ended" ] } }, "required": [ "status" ], "additionalProperties": false }, "hasPaymentMethod": { "type": "boolean" }, "workspaces": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "slug": { "type": "string" }, "name": { "type": "string" }, "storage": { "type": "object", "properties": { "usableGb": { "type": "number" }, "usedBytes": { "type": "number" }, "sampledAt": { "type": "number" }, "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "nextPlan": { "type": "string", "enum": [ "starter", "pro", "max" ] }, "nextUsableGb": { "type": "number" } }, "required": [ "usableGb", "plan" ], "additionalProperties": false } }, "required": [ "id", "slug", "name", "storage" ], "additionalProperties": false } } }, "required": [ "autoTopupThresholdMicroUsd", "monthlySpendMicroUsd", "balanceMicroUsd", "consecutiveTopupFailures", "paidCreditBalanceMicroUsd", "monthlyCreditBalanceMicroUsd", "subscription", "trial", "hasPaymentMethod", "workspaces" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billing /historyRead recent billing ledger entries across funded workspaces.
- Scope
human- Request
- No JSON request body
- Query
Query parameters JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "cursor": { "type": "string", "minLength": 1 }, "limit": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 } }, "additionalProperties": false }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "items": { "type": "array", "items": { "oneOf": [ { "type": "object", "properties": { "id": { "type": "string" }, "amountMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "type": { "type": "string", "enum": [ "grant", "topup", "usage", "adjustment", "reservation", "subscription_grant", "subscription_expiry" ] }, "usageKind": { "type": "string", "enum": [ "vm_compute" ] }, "createdAt": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "scope": { "type": "string", "const": "account" } }, "required": [ "id", "amountMicroUsd", "type", "createdAt", "scope" ], "additionalProperties": false }, { "type": "object", "properties": { "id": { "type": "string" }, "amountMicroUsd": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "type": { "type": "string", "enum": [ "grant", "topup", "usage", "adjustment", "reservation", "subscription_grant", "subscription_expiry" ] }, "usageKind": { "type": "string", "enum": [ "vm_compute" ] }, "createdAt": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "scope": { "type": "string", "const": "workspace" }, "workspaceId": { "type": "string" }, "workspaceName": { "type": "string" } }, "required": [ "id", "amountMicroUsd", "type", "createdAt", "scope", "workspaceId", "workspaceName" ], "additionalProperties": false } ] } }, "nextCursor": { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "null" } ] } }, "required": [ "items", "nextCursor" ], "additionalProperties": false }- Errors
Opaque collection cursor is malformed or bound to another walk (400)
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "error": { "type": "string", "const": "invalid_cursor" } }, "required": [ "error" ], "additionalProperties": false, "description": "Opaque collection cursor is malformed or bound to another walk (400)" }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billing /checkoutCreate a Stripe-hosted setup or credit top-up Checkout session.
- Scope
human- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "workspaceSlug": { "type": "string" }, "amountMicroUsd": { "type": "integer", "minimum": 5000000, "maximum": 9007199254740991 } }, "required": [ "workspaceSlug" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "sessionId": { "type": "string" } }, "required": [ "url" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billing /portalCreate a Stripe-hosted Customer Portal session.
- Scope
human- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "workspaceSlug": { "type": "string" } }, "required": [ "workspaceSlug" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "sessionId": { "type": "string" } }, "required": [ "url" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billing /subscription /checkoutCreate a Stripe-hosted Checkout session for a server-resolved subscription plan. Answers a user API key.
- Scope
human- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "workspaceSlug": { "type": "string" }, "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] } }, "required": [ "workspaceSlug", "plan" ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "sessionId": { "type": "string" } }, "required": [ "url" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Not declared idempotent
/v1 /user /billing /subscription /planUpgrade, schedule a downgrade, or cancel the payer subscription. Answers a user API key.
- Scope
human- Request
Request JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "anyOf": [ { "type": "object", "properties": { "workspaceSlug": { "type": "string" }, "plan": { "type": "string", "enum": [ "starter", "pro", "max" ] } }, "required": [ "workspaceSlug", "plan" ] }, { "type": "object", "properties": { "workspaceSlug": { "type": "string" }, "cancel": { "type": "boolean", "const": true } }, "required": [ "workspaceSlug", "cancel" ] }, { "type": "object", "properties": { "workspaceSlug": { "type": "string" }, "resume": { "type": "boolean", "const": true } }, "required": [ "workspaceSlug", "resume" ] } ] }- Response
Response JSON schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "ok": { "type": "boolean", "const": true } }, "required": [ "ok" ], "additionalProperties": false }- Delivery
- Standard response
- Retry
- Declared idempotent