# Billing and credits

> How /v1 sends and calls are paid for in credits, what each channel costs, reading the balance and the usage report, and the 402 refusals.

Every send and call is paid from the workspace's credit balance, and every
price, preview and report is in **credits**; no answer carries a money amount.
A send's own cost is `billed_credits` on its status (SMS and calls; WhatsApp
sends answer `null`), a campaign preview shows `cost_credits` before you
start it, and `GET /v1/campaigns/{id}/usage` says what a campaign has cost.

## Credits and balance

**Read the balance** with `GET /v1/balance` (any key of the workspace):

```bash
curl https://api.omniocto.com/v1/balance \
  -H "Authorization: Bearer $OMNI_OCTO_API_KEY"
```

```json
{ "object": "balance",
  "credits": 1000, "paid_credits": 800, "bonus_credits": 200,
  "bonus_expires_at": "2026-11-01T00:00:00.000Z",
  "pending_credits": 12.5, "available_credits": 987.5,
  "suspended": false, "suspended_reason": null,
  "tier": 2, "has_payment_method": true, "auto_recharge_disabled": false,
  "plan": { "id": "…", "name": "…", "renews_at": "2026-11-01T00:00:00.000Z" },
  "updated_at": "2026-10-17T10:00:00.000Z" }
```

- `credits` is what the workspace holds.
- `paid_credits` is the part you bought or were granted; it never expires.
- `bonus_credits` is the part included with this billing period; it expires
  at `bonus_expires_at`, and is used first. Both parts are `null` when we
  could not read them just now; otherwise they add up to `credits` (both are
  0 when `credits` is 0 or below).
- `pending_credits` is this period's usage that is not yet taken off
  `credits`.
- `available_credits` is `credits` minus `pending_credits` (never below 0):
  what you can still spend now.
- `updated_at` is when the balance was last read from the billing system: at
  most about 5 minutes ago, so reading it more often than that does not make
  it fresher. Every field:
  [Balance](/platform-api/reference/account/#balance).

## What each channel costs

### SMS and MMS

| what | price |
|---|---|
| SMS to or from a US or Canada number on one of our numbers | 2 credits per segment |
| MMS (picture or video) to a US or Canada number | 4 credits per message |
| MMS to another +1 region (Puerto Rico, Jamaica, …) | 11.5 credits per message |
| SMS to the UK / Germany / anywhere else | 5.7 / 11.5 / 8.6 credits per segment |
| any text on a number you bring from your own carrier account | 0.1 credits per segment or message (your carrier bills the rest) |

A segment is up to 160 plain characters, or 70 when the text has an emoji or
another character outside the GSM alphabet; a longer text is split into
segments. A reply someone sends to one of our numbers costs the same as a
text to them. A send's own cost is `billed_credits` on its status.

Prices, previews and reports are always in **credits**; no response carries a
money amount. A sender's `mode` says whose carrier account it is on:
`"managed"` (one of ours) or `"byo"` (a Twilio, Telnyx or Vonage account you
brought, which also bills you separately).

### WhatsApp

Each WhatsApp message sent from your own WhatsApp Business account (the
usual case) costs 0.1 credits. Meta bills its own charges to you directly;
our credit figure does not include them, so a WhatsApp send's
`billed_credits` is `null`.

### Calls

A call's cost so far is `billed_credits` on `GET /v1/calls/{id}`; it can still
grow for a few minutes after the call ends (speech and the call's analysis are
billed as they finish). Per-minute prices are on Settings → Billing in the
dashboard.

A call placed from a number on your own carrier account (a Twilio account you
brought, or your own SIP carrier) is not charged our phone-line minute,
because your carrier bills you for that minute. It costs a platform fee of
**0.1 credits per started minute** instead, shown in your usage as "Calls on
your own carrier: platform fee". A call nobody answers costs no fee. The
rest of the call (speech, the AI model, the call connection and recording) is
billed the same as on one of our numbers, and its minutes still count toward
the workspace's daily call-minute limit.

## A campaign's cost and spending limit

`GET /v1/campaigns/{id}/usage` says what a campaign has cost so far, by
category, with its calls' minutes or its messages. A preview's `cost_credits`
is the estimate before you start.

`limits.spend_cap_credits` (SMS and AI-call campaigns) caps what a campaign may
spend. When the next call would not fit, the campaign pauses with
`paused_reason` `spend_cap` and its untouched recipients stay `queued`. Calls
under way always finish, so a campaign can end a few calls above its cap; for
a small cap, set a low `limits.max_concurrent_calls` too. Raising the cap is a
person's act: an API key can set one or lower one, but raising or removing it
on a started campaign is `403 spend_cap_raise_needs_person`. Every rule:
[Limits and retries](/platform-api/reference/campaigns/#limits-and-retries).

## When billing refuses a request

A send the balance cannot cover is
`402 insufficient_credits` with `short_credits`, and nothing is sent. A
workspace without a card, or whose subscription has lapsed, is
`402 billing_not_eligible` ([what it means](/platform-api/errors/#sms-messages)).
Add a card or credits in the dashboard under
[Settings → Billing](https://agent.omniocto.com/settings/billing).

## Usage and spend

What the credits went on, for any window, with any key of the workspace:

| route | answers |
|---|---|
| `GET /v1/usage` | the total and the groups: by `category` (default), UTC `day` and/or `label`, up to two (`group_by=day,category`) |
| `GET /v1/usage/events` | one row per charge, newest first, paged (`limit`, `after`) |
| `GET /v1/usage/export` | the same rows as one CSV file |

```bash
curl "https://api.omniocto.com/v1/usage?group_by=day,category&from=2026-10-01" \
  -H "Authorization: Bearer $OMNI_OCTO_API_KEY"
```

```json
{ "object": "usage_report",
  "from": "2026-10-01T00:00:00.000000Z", "to": "2026-10-29T06:00:00.000000Z",
  "cycle_start": "2026-10-01T00:00:00.000000Z", "cycle_end": "2026-11-01T00:00:00.000000Z",
  "category": null, "group_by": ["day", "category"],
  "total_credits": 1234.5, "events": 812,
  "groups": [
    { "day": "2026-10-18", "category": "phone", "label": null,
      "credits": 96, "units": null, "unit": null, "events": 40 } ] }
```

- **The window.** `from` is inclusive and `to` exclusive, each an ISO 8601
  date (midnight UTC) or instant, at most 400 days apart. Without them the
  window is the current billing cycle (`cycle_start`) to now: the usage
  `pending_credits` on `/v1/balance` counts. A workspace with no billing
  period counts the last 30 days.
- **The same rows on all three.** `/v1/usage` echoes the `from` and `to` it
  read. Pass that pair to `/v1/usage/events` and `/v1/usage/export` and they
  answer the same charges: the export's row count equals `events`. To page
  through a fixed set, always pass `from` and `to`; a default window is
  worked out again on every page. A charge is recorded a moment before it is
  committed, so for a complete set for your books pass a `to` a few minutes
  in the past.
- **Categories.** `phone` (call minutes, attempts, voicemails), `recording`
  (call connection and recording), `speech` (speech-to-text, text-to-speech,
  realtime voice), `ai` (AI model tokens), `messaging` (SMS, MMS and chat
  channel messages), `whatsapp`, `email`, `numbers` (number rental) and
  `other`. Filter with `category=`. The same words as a campaign's usage.
- **Labels and units.** `label` says what was charged (`SMS outbound (US)`,
  `AI Tokens (input)`); new labels appear as we bill new things. `units` and
  `unit` (`message`, `minute`, `1k_tokens`, …) are filled only when you
  group by `label`: 3 messages and 3 minutes are not 6 of anything.
- **Totals.** Each group's `credits` is rounded to 4 decimals, so the groups
  add up to `total_credits` give or take that rounding. A day with no usage
  has no group. More than 5,000 groups is `400 too_many_groups`: narrow the
  window or group by less.
- **One charge to its conversation.** Each row of `/v1/usage/events` carries
  `conversation_id` (read it with `GET /v1/conversations/{id}`), `agent_id`
  and `campaign_id`.
- **The export** holds at most 250,000 rows; over that it is refused before
  any byte (`400 export_too_large`), never cut short. One export runs at a
  time per workspace (`429 export_in_progress`, retry after `Retry-After`).
  If the connection is cut mid-file the body is incomplete: compare the row
  count to `events`.

The `get_usage` [MCP tool](/mcp/api-tools/) answers `GET /v1/usage`. Every
field: [UsageReport](/platform-api/reference/account/#usagereport).