Skip to content
Docs
opendesk.shOpen app

Billing

Plans, seats, AI usage in US dollars, AI top-ups, auto-recharge and Stripe checkout on opendesk Cloud.

Cloud only. Billing exists only on opendesk Cloud. See Cloud plans and billing. On an instance without Stripe configured, every route below returns 404 billing_disabled, except GET /v1/billing, which returns the self-hosted entitlements (plan self_hosted, no limits and unlimited AI), and GET /v1/billing/usage.

Method Path Who Description
GET /v1/billing member The workspace’s plan, limits, usage and AI balance
POST /v1/billing/checkout owner, admin { interval: "month" | "year" } → { url } to Stripe Checkout
POST /v1/billing/portal owner, admin { url } to the Stripe customer portal
POST /v1/billing/top-up/checkout owner, admin { top_ups: 1..20 } → { url } to a one-time Checkout for AI top-ups ($10 of AI usage each)
PATCH /v1/billing/auto-recharge owner, admin { enabled?, threshold_usd?, top_ups?, monthly_limit? } → the billing object. See Auto-recharge
POST /v1/billing/payment-method owner, admin { url } to Stripe Checkout in setup mode, to save a card
GET /v1/billing/usage?from=&to= admin AI usage by feature, day, member and model; also on self-hosted

Checkout returns to /settings/billing?checkout=success|canceled, an AI top-up to ?top_up=success|canceled, and saving a card to ?payment_method=success|canceled. The plan and balance change when Stripe confirms the payment, not on the redirect: wait for billing.updated, or fetch GET /v1/billing again.

Every amount ending in _usd, or named usd, is US dollars, a number with up to 6 decimals.

interface Billing {
plan: "free" | "pro" | "self_hosted";
billing_enabled: boolean;
status: "none" | "trialing" | "active" | "past_due" | "unpaid" | "canceled" | "incomplete" | "incomplete_expired" | "paused";
interval: "month" | "year";
seats: number; // billed seats in Stripe
billed_seats: number; // current owners, admins and agents
current_period_end: string | null;
cancel_at_period_end: boolean;
limits: { max_mailboxes: number; max_members: number }; // 0 = unlimited
usage: { mailboxes: number; members: number };
ai: {
unlimited: boolean; // self-hosted: no allowance, nothing refused
monthly_usd: number; // the monthly allowance: $0.50 on Free, $10 per paid seat on Pro
used_usd: number; // used of the allowance this period
purchased_usd: number; // AI top-ups left; they never expire and are spent after the allowance
remaining_usd: number; // what is left of the allowance, plus purchased_usd
period_end: string; // the allowance resets at the start of each calendar month, UTC
};
prices: { pro_month: Price | null; pro_year: Price | null; ai_top_up: Price | null };
auto_recharge: AutoRecharge | null; // null when billing is off
}
interface Price {
id: string;
unit_amount: number; // in minor units, such as cents
currency: string;
interval: "month" | "year" | null;
usd?: number; // ai_top_up: the AI usage one top-up adds (10)
}
interface AutoRecharge {
enabled: boolean; // off by default
threshold_usd: number; // recharge when the AI balance falls below this (default 2)
top_ups: number; // AI top-ups per recharge, 1..20 (default 1)
monthly_limit: number; // recharges per calendar month, UTC, 1..30 (default 5)
recharges_this_month: number;
low_at_usd: number; // a balance below this reads as running low
last_recharge: { at: string; top_ups: number; usd: number; amount: number; currency: string } | null; // amount charged, in minor units
failure: { code: "card_declined" | "authentication_required" | "no_payment_method" | "payment_failed" | "unavailable"; at: string } | null;
}
  • Every feature is on every plan. Plans differ only in limits.
  • Seats are members with the owner, admin or agent role. Viewers are free.
  • When a limit is reached, creating another mailbox or member returns 402 plan_limit, with the limit named in detail. Nothing existing is removed.
  • AI usage is charged in US dollars on each call’s real cost: the provider’s cost at the model’s list price, times 1.5 on opendesk Cloud, kept to $0.000001. The charge depends on the model the workspace chose (see Models) and on the size of the request.
  • A call starts only when the balance covers its estimate: the prompt tokens at the model’s input price plus the call’s maximum output tokens (counted up to 4,000) at its output price, times the markup, and at least $0.01. Otherwise the route returns 402 ai_balance_empty and the model is not called. If a call ends up costing more than what is left, the balance goes to $0.
  • The allowance is spent first, then AI top-ups, which never expire. A failed call costs nothing. Search indexing is free.
  • Calls on the workspace’s own provider key cost $0 and are never refused for the balance; only Jev decisions are still charged.

POST /v1/billing/top-up/checkout with { "top_ups": 2 } opens a one-time Checkout for 2 AI top-ups ($20 of AI usage). Each top-up adds $10 of AI usage, dollar for dollar. The card is saved for auto-recharge. 422 top_up_unavailable means the instance offers no AI top-ups.

Off by default. When on, a charged AI call that leaves the balance below threshold_usd buys top_ups AI top-ups with the saved card, at most monthly_limit times a calendar month. The defaults are 1 top-up ($10) when the balance falls below $2, at most 5 times a month.

PATCH /v1/billing/auto-recharge takes:

Field Values
enabled true or false
threshold_usd 0.01 to 1,000, rounded to cents
top_ups 1 to 20
monthly_limit 1 to 30
  • 409 payment_method_required: turning it on needs a saved card. POST /v1/billing/payment-method saves one.
  • 422 recharge_too_small: each recharge must bring the balance back above the threshold, so top_ups × $10 must be more than threshold_usd.
  • When a payment fails, auto-recharge turns itself off and records failure. Owners and admins get an auto_recharge_failed notification and an email. Turning it back on clears failure.

Owners and admins get an ai_balance_low notification ({ remaining_usd }) when the balance falls below low_at_usd: the threshold, but never more than a fifth of the monthly allowance (at least $0.05). They get ai_balance_out ({ remaining_usd }), and an email, when it falls below $0.01. Each is sent at most once a month, and any top-up or recharge resets both.

GET /v1/billing/usage?from=&to= (RFC 3339 or YYYY-MM-DD; this month by default, at most a year). A YYYY-MM-DD to includes that day.

interface UsageReport {
from: string;
to: string; // exclusive
total_usd: number;
total_calls: number;
by_feature: { feature: string; usd: number; calls: number }[];
by_day: { date: string; usd: number; calls: number; by_feature: Record<string, number> }[]; // days with usage only; by_feature in US dollars
by_member: { user: UserRef | null; usd: number; calls: number }[]; // null: the AI agent and automations
by_model: { model: string; usd: number; calls: number; input_tokens: number; output_tokens: number; source: "instance" | "own_key" }[];
own_key_calls: number; // calls on the workspace's own provider key, $0 each
}

Changes publish a billing.updated event with the billing object.

Edit this page on GitHub