# 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](/guides/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](#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.

## The billing object

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

```ts
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;
}
```

## Plans and limits

- 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

- 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](/api/resources/ai/#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.

## AI top-ups

`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.

## Auto-recharge

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.

## Usage report

`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.

```ts
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.
