> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Billing and usage

Every Jelliu workspace runs on a plan. Plans do not differ by which features they unlock: every product capability is available on every paid plan, with one exception (custom cloned voices, Enterprise only). Plans differ by **how much you can use**: call minutes, AI replies on text channels, agents, campaigns, contacts, phone numbers, concurrent calls, knowledge base size and connected apps.

This page shows how to read that from the API so your integration can check headroom before it acts, and how to handle the responses when a limit is reached.

## How it works

```mermaid
flowchart TD
  R[Your request] --> G{Plan check}
  G -- "Within limits" --> OK[Request proceeds]
  G -- "Cap reached" --> E["403 BILLING_ERROR<br />metadata: limit, current, tier"]
  OK --> U[Usage recorded:<br />call minutes, AI replies]
  U --> A{Thresholds}
  A -- "80%" --> N1[In-app warning]
  A -- "100% of minutes" --> OV[Overage, up to the ceiling]
  A -- "100% of AI replies" --> STOP[Agent stops replying on text channels]
```

Limits are checked at two moments:

* **When you create something** that counts against the plan (an agent, a campaign, contacts, a knowledge file, a connected app, a phone number). If the cap is reached, creation fails with `403 BILLING_ERROR`.
* **When the platform consumes something** (a call is placed or answered, an agent replies on a text channel). If the allowance is used up, the call is refused or the agent does not reply.

## Endpoints

| Method | Path                           | Returns                                                             | Key needed |
| ------ | ------------------------------ | ------------------------------------------------------------------- | ---------- |
| `GET`  | `/api/billing/subscription`    | Plan, status and period                                             | `read`     |
| `GET`  | `/api/billing/usage`           | Minutes and AI replies used in the current window                   | `full`     |
| `GET`  | `/api/billing/limits`          | Every limit and feature flag of the plan                            | `full`     |
| `GET`  | `/api/billing/ai-usage`        | Weekly credits for the in-product AI assistant and prompt generator | `read`     |
| `GET`  | `/api/billing/invoices`        | The 24 most recent invoices                                         | `full`     |
| `GET`  | `/api/billing/charges`         | The 24 most recent charges                                          | `full`     |
| `GET`  | `/api/billing/payment-methods` | Saved cards                                                         | `full`     |

Usage, limits, invoices, charges and payment methods are financial information, restricted to workspace administrators and the billing role. For an API key that means the `full` scope; a `read` or `write` key receives `403` with `This operation requires an API key with the 'full' scope`. See [API keys](/platform/api-keys).

All `/api/billing` routes share a limit of **15 requests per minute** per workspace. Read limits once and cache them rather than calling before every operation.

### Get the subscription

`GET /api/billing/subscription`

**`cURL`**

```bash title="cURL"
curl -sS "https://api.jelliu.co/api/billing/subscription" \
  -H "Authorization: Bearer $JELLIU_API_KEY"
```

**`Node.js`**

```javascript title="Node.js"
const res = await fetch('https://api.jelliu.co/api/billing/subscription', {
  headers: { Authorization: `Bearer ${process.env.JELLIU_API_KEY}` },
});
const { data } = await res.json();
if (!data.subscription) {
  console.log('No active plan');
} else {
  console.log(data.subscription.tier, data.subscription.status);
}
```

**`Python`**

```python title="Python"
import os
import requests

res = requests.get(
    "https://api.jelliu.co/api/billing/subscription",
    headers={"Authorization": f"Bearer {os.environ['JELLIU_API_KEY']}"},
    timeout=30,
)
sub = res.json()["data"]["subscription"]
print("No active plan" if sub is None else (sub["tier"], sub["status"]))
```

```json
{
  "data": {
    "subscription": {
      "tier": "growth",
      "status": "active",
      "current_period_end": "2026-10-03T14:12:09.000Z",
      "cancel_at_period_end": false,
      "trial_end": null,
      "max_agents": 5,
      "max_concurrent_calls": 10,
      "included_minutes": 620
    }
  }
}
```

| Field                                                    | Description                                                                                                      |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `tier`                                                   | `starter`, `growth`, `business` or `enterprise`.                                                                 |
| `status`                                                 | `active`, `trialing` or `past_due`. Other states are not active, and the response is `{ "subscription": null }`. |
| `current_period_end`                                     | End of the current billing period.                                                                               |
| `cancel_at_period_end`                                   | `true` if the plan was cancelled and ends at `current_period_end`.                                               |
| `trial_end`                                              | End of the trial, or `null`.                                                                                     |
| `max_agents`, `max_concurrent_calls`, `included_minutes` | Base plan values. For the effective agent limit including add-ons, read `/api/billing/limits`.                   |

### Get usage

`GET /api/billing/usage`

**`cURL`**

```bash title="cURL"
curl -sS "https://api.jelliu.co/api/billing/usage" \
  -H "Authorization: Bearer $JELLIU_API_KEY"
```

**`Node.js`**

```javascript title="Node.js"
const res = await fetch('https://api.jelliu.co/api/billing/usage', {
  headers: { Authorization: `Bearer ${process.env.JELLIU_API_KEY}` },
});
const { data: usage } = await res.json();
console.log(`${usage.minutesUsed} of ${usage.includedMinutes + usage.bonusMinutes} minutes`);
console.log(`${usage.chatMessagesUsed} AI replies used`);
```

**`Python`**

```python title="Python"
import os
import requests

res = requests.get(
    "https://api.jelliu.co/api/billing/usage",
    headers={"Authorization": f"Bearer {os.environ['JELLIU_API_KEY']}"},
    timeout=30,
)
usage = res.json()["data"]
print(f"{usage['minutesUsed']} of {usage['includedMinutes'] + usage['bonusMinutes']} minutes")
print(f"{usage['chatMessagesUsed']} AI replies used")
```

```json
{
  "data": {
    "minutesUsed": 512,
    "includedMinutes": 620,
    "bonusMinutes": 0,
    "overageMinutes": 0,
    "overageCostCents": 0,
    "usagePercentage": 82.58,
    "isTrialing": false,
    "trialMinutesLeft": 0,
    "alertTriggered": true,
    "chatMessagesUsed": 2210,
    "includedChatMessages": 4000,
    "chatUsagePercentage": 55.25,
    "channels": ["voice", "whatsapp", "webchat", "email"],
    "excludedMetrics": []
  }
}
```

| Field                            | Description                                                                             |
| -------------------------------- | --------------------------------------------------------------------------------------- |
| `minutesUsed`                    | Call minutes consumed in the current usage window.                                      |
| `includedMinutes`                | Minutes included in the plan for the window. During a trial, the trial allowance.       |
| `bonusMinutes`                   | Remaining minutes from purchased minute packs. They sit on top of the included minutes. |
| `overageMinutes`                 | Minutes beyond included plus bonus minutes. Always `0` during a trial.                  |
| `overageCostCents`               | The overage minutes priced at your plan's overage rate, in US cents.                    |
| `usagePercentage`                | `minutesUsed` over included plus bonus minutes, as a percentage (can exceed 100).       |
| `isTrialing`, `trialMinutesLeft` | Trial state and the minutes left in it.                                                 |
| `alertTriggered`                 | `true` once usage reaches 80% of included plus bonus minutes.                           |
| `chatMessagesUsed`               | AI replies sent on text channels in the window.                                         |
| `includedChatMessages`           | The plan's AI reply allowance. `-1` means unlimited.                                    |
| `chatUsagePercentage`            | 0 to 100, or `0` when the allowance is unlimited.                                       |
| `channels`                       | Channels available on the plan.                                                         |

Without an active plan, every number is `0` and `channels` is `["voice"]`. The response is cached for up to 30 seconds.

### Get limits

`GET /api/billing/limits` returns the effective limits for the workspace, including purchased agent add-ons, and the plan's feature flags. It is cached for up to 5 minutes (`Cache-Control: private, max-age=300`), so a plan change can take that long to show.

```json
{
  "data": {
    "tier": "growth",
    "maxAgents": 5,
    "maxConcurrentCalls": 10,
    "includedMinutes": 620,
    "maxContacts": 2000,
    "maxCampaigns": 3,
    "maxKnowledgeFiles": 25,
    "maxKnowledgeBytes": 52428800,
    "maxTeamMembers": 10,
    "maxConnectors": 5,
    "maxMcpServersPerAgent": 3,
    "audioRetentionDays": 30,
    "maxIntegrations": 9999,
    "maxPhoneNumbers": 3,
    "includedChatMessages": 4000,
    "channels": ["voice", "whatsapp", "webchat", "email"],
    "features": {
      "voiceCloning": false,
      "advancedDashboard": true,
      "abTesting": true,
      "reportExport": true,
      "customReports": true,
      "webhook": true,
      "mcpGateway": true,
      "transcriptRetentionDays": 30
    }
  }
}
```

The example shows a subset of `features`; the real object has more flags. A negative value (`-1`) means unlimited. Very large values such as `9999` are also effectively unlimited.

When the workspace has **no active plan** (the trial ended, the plan was cancelled, or it never subscribed), `tier` is `"none"`, every count limit is `0` and `channels` is empty. The workspace can still use the dashboard and choose a plan, but it cannot create resources, place calls or get AI replies.

## Plan limits

The limits in effect when this page was written. **Read `/api/billing/limits` for the values that apply to your workspace**; they are the ones Jelliu enforces.

| Limit                                 | Starter | Growth  | Business  | Enterprise               |
| ------------------------------------- | ------- | ------- | --------- | ------------------------ |
| Agents                                | 2       | 5       | 12        | By contract              |
| Concurrent calls                      | 3       | 10      | 25        | By contract              |
| Call minutes included per month       | 200     | 620     | 1,400     | By contract              |
| AI replies on text channels per month | 1,200   | 4,000   | 9,000     | Unlimited                |
| Active campaigns                      | 1       | 3       | Unlimited | Unlimited                |
| Contacts                              | 500     | 2,000   | 20,000    | By contract              |
| Knowledge files                       | 5       | 25      | 100       | By contract              |
| Knowledge base size                   | 10 MB   | 50 MB   | 200 MB    | Unlimited                |
| Connected apps                        | 2       | 5       | 10        | Unlimited                |
| MCP servers per agent                 | 1       | 3       | 10        | Unlimited                |
| Phone numbers                         | 1       | 3       | 10        | By contract              |
| Team members                          | 5       | 10      | 20        | 20                       |
| Transcript retention                  | 7 days  | 30 days | 90 days   | Unlimited                |
| Call audio retention                  | 7 days  | 30 days | 90 days   | Kept until you delete it |

Agent add-ons raise the agent limit on Starter, Growth and Business; `maxAgents` in `/api/billing/limits` already includes them.

## How consumption works

### Call minutes

* Included minutes are a **monthly** allowance. On an annual plan, the usage window is the current month inside the annual period, so minutes reset every month.
* Minute packs add bonus minutes on top of the plan's included minutes.
* Past included plus bonus minutes, calls continue as **overage**, billed automatically to the card on file.
* Overage has a **ceiling**. On Starter, Growth and Business, calls are refused once usage reaches the included minutes plus purchased pack minutes plus half of the included minutes again (for example, 300 minutes on a 200-minute plan with no packs). Buying a minute pack or upgrading raises the ceiling; otherwise it resets with the next usage window. Enterprise has no ceiling.
* **Trials** are capped at 15 minutes of calls and never generate overage. A single call is also cut off when the trial minutes run out.

### AI replies on text channels

* Each AI reply on WhatsApp, email, web chat, Instagram or Messenger counts against `includedChatMessages`. A reply is counted only after it is actually produced for delivery, so failed sends do not consume allowance.
* The allowance is **not billed per message** and has no overage. When it is used up, the agent **stops replying** on text channels until the window renews or the plan is upgraded. Voice calls are not affected.
* Separately, WhatsApp and email have a daily per-workspace send cap as a safety limit. See [WhatsApp](/channels/whatsapp) and [Email](/channels/email).

### Notifications

The workspace receives in-app notifications at **80%** and **100%** of both call minutes and AI replies, and when calls are paused at the overage ceiling. There is no webhook for usage thresholds yet: `usage.threshold_reached` appears in the [event catalog](/webhooks#event-catalog) but is not sent. Poll `/api/billing/usage` if you need to react programmatically.

## Handling limit errors

Plan limits return `BILLING_ERROR`. The HTTP status is `403` for most limits and `402` for phone numbers and for plan changes without a payment method. Branch on `error.code`, and use `metadata` to show the user what happened.

### Resource caps

Creating an agent, campaign, contact, knowledge file, connected app or MCP server assignment beyond the plan's cap:

```json
{
  "error": {
    "code": "BILLING_ERROR",
    "message": "Agent limit reached (5 agents on the growth plan). Upgrade your plan in Settings → Plan or purchase agent add-ons.",
    "metadata": { "limit": 5, "current": 5, "tier": "growth" }
  }
}
```

A bulk contact import that would cross the cap is rejected as a whole, and reports the batch size:

```json
{
  "error": {
    "code": "BILLING_ERROR",
    "message": "Import of 800 contacts would exceed the plan limit (2000 contacts on the growth plan; currently 1500). Reduce the batch or upgrade your plan in Settings → Plan.",
    "metadata": { "limit": 2000, "current": 1500, "batch": 800, "tier": "growth" }
  }
}
```

| `metadata` key | Meaning                                            |
| -------------- | -------------------------------------------------- |
| `limit`        | The plan's cap.                                    |
| `current`      | How many exist now.                                |
| `batch`        | Items in the rejected request, when more than one. |
| `tier`         | The workspace's plan, or `none`.                   |

Messages you may see, verbatim:

| Limit           | `message`                                                                                                       |
| --------------- | --------------------------------------------------------------------------------------------------------------- |
| Agents          | `Agent limit reached (… agents on the … plan). Upgrade your plan in Settings → Plan or purchase agent add-ons.` |
| Campaigns       | `Campaign limit reached (… campaigns on the … plan). Upgrade your plan in Settings → Plan.`                     |
| Contacts        | `Contact limit reached (… contacts on the … plan). Upgrade your plan in Settings → Plan.`                       |
| Knowledge files | `Knowledge file limit reached (… files on the … plan). Upgrade your plan in Settings → Plan for more files.`    |
| Knowledge size  | `Knowledge base size limit reached (… MB on the … plan). Upgrade your plan in Settings → Plan for more.`        |
| Connected apps  | `Connected apps limit reached (… on the … plan). Upgrade your plan in Settings → Plan for more.`                |
| No active plan  | `Your free trial has ended (or no plan is active). Choose a plan in Settings → Plan to continue.`               |

### Calls refused

`POST /api/calls` is refused with `403 BILLING_ERROR` when the workspace has no active plan, the trial minutes are used up, or the overage ceiling is reached:

```json
{
  "error": {
    "code": "BILLING_ERROR",
    "message": "You have used all your trial minutes. Upgrade to a paid plan in Settings → Plan to keep calling.",
    "metadata": { "tier": "starter" }
  }
}
```

This route returns the same message for an exhausted trial and for a paid plan that reached its overage ceiling. Do not rely on the wording: check `/api/billing/usage` (`isTrialing`, `minutesUsed`, `includedMinutes`, `bonusMinutes`) to tell the cases apart.

Campaign calls are checked the same way before each dial. Concurrency is separate: exceeding the plan's concurrent calls returns `429 MAX_CONCURRENT_CALLS_REACHED` and is safe to retry. See [Errors](/errors).

### AI replies exhausted

When the AI reply allowance is used up, the agent does not answer on text channels. The workspace gets an in-app notification, and the error recorded for the conversation is in Spanish, as it is shown to the workspace owner:

* With a plan: `Llegaste al límite mensual de mensajes de IA (… mensajes del plan …). Sube de plan en Ajustes → Plan.`
* Without a plan: `Tu plan no está activo, así que la IA no está respondiendo en este canal. Elige un plan en Ajustes → Plan para volver a activarla.`

Visitors on the web chat widget never see this text; the widget shows its own neutral message.

### Phone numbers

Adding a phone number beyond `maxPhoneNumbers` returns `402 BILLING_ERROR`:

```json
{
  "error": {
    "code": "BILLING_ERROR",
    "message": "Your growth plan allows up to 3 phone numbers and this workspace already has 3. Assign an existing number to this agent under Numbers, or upgrade your plan.",
    "metadata": { "tier": "growth" }
  }
}
```

The wording depends on the path (provisioning, bringing your own number, or when the plan still allows buying add-on numbers), so rely on the code and status. See [Phone numbers](/resources/phone-numbers).

### Feature gates

A few routes check a plan feature flag. When the plan does not include it, they return `403 BILLING_ERROR` with `metadata.tier` and a message such as `Advanced analytics is not available on the none plan. Upgrade your plan to use it.` All current plans include these features:

| Feature flag        | Routes                                                                                          |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| `advancedDashboard` | `/api/analytics/*` (the lightweight `/api/dashboard` is not gated)                              |
| `customReports`     | `POST /api/reports`, `GET /api/reports/types`                                                   |
| `reportExport`      | `POST /api/exports`                                                                             |
| `webhook`           | `/api/webhooks` management (deliveries to existing webhooks are never stopped by a plan change) |
| `abTesting`         | `/api/agents/{agentId}/ab-tests`                                                                |
| `mcpGateway`        | The [MCP server](/mcp) (JSON-RPC error `-32003`)                                                |

The one feature that differs by plan is **custom cloned voices**. Assigning a cloned voice to an agent outside Enterprise returns `403 BILLING_ERROR`: `Custom cloned voices are an Enterprise feature (not available on the … plan). Upgrade to use a cloned brand voice.`

## In-product AI credits

The dashboard's AI assistant and prompt generator have their own weekly budget, separate from agent replies. `GET /api/billing/ai-usage` returns it in credits (1 credit is 1,000 tokens):

```json
{
  "data": {
    "weekStart": "2026-09-14",
    "resetsAt": "2026-09-21T00:00:00.000Z",
    "ask_ai": { "used": 41, "limit": 660, "remaining": 619 },
    "prompt_gen": { "used": 12, "limit": 248, "remaining": 236 }
  }
}
```

`limit` and `remaining` are `-1` when unlimited. When a budget is used up, those features return `429 AI_WEEKLY_CAP_REACHED` with `metadata.kind`, `metadata.tier`, `metadata.weekStart` and `metadata.limitTokens`. Your agents keep working.

## Invoices and charges

`GET /api/billing/invoices` and `GET /api/billing/charges` return the 24 most recent items, newest first. Amounts are in the smallest currency unit (cents).

| Invoice field                                                                                                | Charge field                                                                 |
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `id`, `number`, `status`, `amountDue`, `amountPaid`, `currency`, `created`, `hostedInvoiceUrl`, `invoicePdf` | `id`, `amount`, `currency`, `status`, `description`, `created`, `receiptUrl` |

Both return `[]` if the workspace has no billing account yet. Responses are sent with `Cache-Control: no-store`.

## Changing the plan and buying add-ons

Choosing or changing a plan, buying minute packs, agent add-ons or extra phone numbers, and managing cards are done in the dashboard under **Settings → Plan**. These operations are restricted to the workspace owner and the billing role, are not available through the [MCP server](/mcp), and several of them hand off to a hosted payment page that a person must complete. Do not automate them from an integration.

A good integration pattern: read `/api/billing/limits` once when your integration starts (and again after any `BILLING_ERROR`), check `/api/billing/usage` before launching a large campaign, and surface `BILLING_ERROR` messages to an administrator with a link to **Settings → Plan** rather than retrying.

## Errors

| Status | Code                           | When                                                                                                                     |
| ------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `402`  | `BILLING_ERROR`                | Phone number cap reached, or a plan change without a payment method (`Add a payment method before changing your plan.`). |
| `403`  | `BILLING_ERROR`                | A resource cap, a refused call, or a plan feature gate.                                                                  |
| `403`  | `FORBIDDEN`                    | The key lacks the `full` scope for usage, limits, invoices, charges or payment methods.                                  |
| `429`  | `MAX_CONCURRENT_CALLS_REACHED` | The plan's concurrent call limit. Retry later.                                                                           |
| `429`  | `AI_WEEKLY_CAP_REACHED`        | The weekly in-product AI budget is used up.                                                                              |
| `429`  | `RATE_LIMIT_EXCEEDED`          | More than 15 requests per minute on `/api/billing`. Honor `Retry-After`.                                                 |

`BILLING_ERROR` is not retryable: the same request fails until the plan, the usage window or the resource count changes.

## Related

#### [API keys](/platform/api-keys)

Why usage and limits need a `full` key.

#### [Errors](/errors)

The error envelope and the `metadata` allowlist.

#### [Rate limits](/rate-limits)

Request budgets, which scale with the plan.

#### [Analytics and reports](/platform/analytics-and-reports)

Operational metrics for calls and conversations.