# Limits & pricing (/docs/gateway/limits-pricing)

Rate limits, spend caps, task caps, the cost receipt and the error contract.

codai bills per **verified successful task**, not per token — the plan comparison is on [codai.ro/pricing](https://codai.ro/pricing) and your live spend is in [hub.codai.ro](https://hub.codai.ro). This page covers what the gateway enforces on every request and how it tells you when you hit a wall.

## Rate limits [#rate-limits]

Limits are sliding-window and come from your plan. Each key has its own window (keys minted by an app share one window per subscription). These are the plan values:

<PlanLimitsTable locale="en" />

- **Requests per hour** is a backstop of 30 × the per-minute limit.
- **Input tokens per minute** is the default for every plan; a key can carry a lower limit set in the hub.
- **Own MCP servers** is how many MCP servers of your own you can connect; a dash means the plan does not include them.
- The table is generated from the same plan catalog as [codai.ro/pricing](https://codai.ro/pricing) and `codai.ro/pricing.json`.

Each response carries the current state. Example for a Builder key:

```text
x-ratelimit-limit-minute: 100
x-ratelimit-remaining-minute: 97
x-ratelimit-reset-minute: 1790000060
x-ratelimit-limit-hour: 3000
x-ratelimit-remaining-hour: 2951
x-ratelimit-reset-hour: 1790003600
```

`reset` values are Unix timestamps in seconds. Read the limits from the headers rather than hard-coding them: a per-key limit set in the hub, or a plan change, takes effect without a client change.

Three windows are enforced:

| Window | What it counts | Error code |
| --- | --- | --- |
| Requests per minute / per hour | Every `/v1/*` and `/mcp` request. | `rate_limit_exceeded` |
| Input tokens per minute | Prompt tokens actually recorded, with cache **writes** at 25 % weight and cache **reads** excluded entirely. 1 500 000/min unless the key sets a lower limit. | `rate_limit_exceeded` |
| Output tokens per minute | Completion tokens, per user across all keys: 20 000/min on Free, 100 000/min on paid plans. | `rate_limit_exceeded` |

Because cache reads are free for the token window, keeping [prompt caching](./headers.mdx#caching) on is also how you stay under the limit in long agent sessions.

### Honour `Retry-After` [#honour-retry-after]

On every `429` the gateway sets `Retry-After` to the **exact** number of seconds until the oldest entry ages out of the window — not a flat 60. Clients that honour it find a free slot on the first retry; clients that ignore it hammer the gateway and stay blocked. The [SDKs](../sdks/index.mdx) retry `429` and `5xx` with backoff automatically.

## Task caps [#task-caps]

A **task** starts on a fresh user turn; tool-result continuations and follow-ups inside the same conversation do not start new ones. Plans include a number of **verified tasks per month**:

| Plan | Verified tasks per month |
| --- | --- |
| Free | 5 |
| Starter | 33 |
| Builder | 83 |
| Team | 250 |

Past the allowance, each verified task is billed as overage at **€1.20**, when your billing allows overage. The €1.20 covers up to €1.20 of model usage for that task; unusually large tasks (above €1.20 of model usage) are billed for the excess at standard token rates, never below 1.2× upstream cost. Details in [tasks and spend](../agents/tasks-and-spend.mdx). When nothing pays for a new task:

- The gateway returns `429` with code `quota_exceeded` and a `Retry-After` pointing at the reset.
- **Conversations you already started keep working** — only brand-new tasks are paused.
- Utility calls on `codai-fast` with `X-Codai-No-Task: 1`, embeddings, audio and realtime never count.

`quota_exceeded` is deliberately a `429`, not a `403`: IDE clients treat it as "back off and retry later" instead of retrying every few seconds.

## Spend caps [#spend-caps]

Accounts may carry a **daily** and a **monthly** spend cap, set in EUR in the hub and summed across all keys. The cap is a guardrail, not an invoice: the gateway compares it one-to-one with the upstream cost it measures, which is in USD — hence the `-usd` header names. When enabled, each response reports where you stand:

```text
x-codai-spend-day-usd: 3.42
x-codai-spend-day-cap-usd: 20.00
x-codai-spend-month-usd: 41.90
x-codai-spend-month-cap-usd: 300.00
```

Crossing a cap returns `429 rate_limit_exceeded` with a message naming the window (daily caps reset at midnight UTC, monthly on the 1st UTC). Set per-key budgets in the hub under **Keys** — the key's budget is a floor under the account cap, never above it.

### Images [#images]

[Image generation and editing](./embeddings-audio.mdx#image-generation) are billed from the **token usage the model reports**, at the Vertex rates of the model that served the call — output image tokens, input text and input/reference images. Approximate price of one 1K image with no reference images:

| `model` | ≈ USD per 1K image |
| --- | --- |
| `codai-image` (default, EU) | $0.074 |
| `codai-image-fast` | $0.034 |
| `codai-image-pro` | $0.134 |

Higher `image_size` values and reference images cost more. The charge is reported in `x-codai-cost-micro-usd`, recorded in the receipt, and **counts toward the daily and monthly spend caps** like any other cost. On prepaid plans the **EUR wallet is debited** for images too, at that USD cost converted to EUR with a 1.3 service multiplier (≈ €0.083 for a default image). Image calls never start a task, so they do not use your task allowance. A request whose images are all blocked by the safety filter (`content_filtered`) costs nothing.

## Per-request ceiling [#per-request-ceiling]

A single top-level request — including any best-of candidates, judge calls and sub-agent delegations the gateway spawns for it — aborts early with a partial answer once its running cost crosses **5 USD**. Plain chat never gets near this; it exists so a runaway agent loop cannot bill an unbounded amount in one call.

## Usage-policy check [#usage-policy-check]

Agent steps that may break the model providers' usage policies are reviewed before they are sent upstream. A step judged a false positive goes through unchanged. Otherwise the gateway does **not** return an error: the response is a normal `200` assistant message that tells the agent what legitimate context is missing (whose system, what authorisation, what defensive purpose) or which part to drop. After **3 consecutive** flagged steps within 24 hours the message tells the agent to stop pursuing that goal, and further suspicious steps are declined the same way until the window expires; unrelated work is unaffected. These responses carry `x-codai-policy` (`ambiguous`, `violating`, `refused`, `refused-remembered`, or `cleared` for a reviewed false positive) and `x-codai-policy-strikes`, and cost nothing. Rewording a request to get around the check counts as another strike.

## Body size [#body-size]

Request bodies on `/v1/*` are capped at **12 MB**. Larger bodies get `413` with code `payload_too_large`. Audio uploads are separately capped at 25 MB on `/v1/audio/transcriptions`.

## Cost receipt [#cost-receipt]

Every chat or messages response carries the cost of *that* request when it can be known before the first byte:

```text
x-codai-upstream: claude-opus-5-5
x-codai-cost-micro-usd: 18420
```

Micro-USD are integers — `18420` is $0.01842. On native streams the count is not known at header time, so you get `x-codai-cost-estimate: pending` instead and the exact figure lands in the receipt endpoint:

```bash
# last 24 hours (default)
curl https://ai.codai.ro/v1/receipt -H "Authorization: Bearer $CODAI_API_KEY"

# one session
curl "https://ai.codai.ro/v1/receipt?session_id=my-project" -H "Authorization: Bearer $CODAI_API_KEY"

# since a timestamp
curl "https://ai.codai.ro/v1/receipt?since=2026-09-01T00:00:00Z" -H "Authorization: Bearer $CODAI_API_KEY"
```

```json
{
  "events": 142,
  "tasks": 9,
  "cost_micro_usd_total": 1834200,
  "cost_usd": 1.8342,
  "prompt_tokens": 2210034,
  "completion_tokens": 41890,
  "cached_read_tokens": 1988001,
  "cache_write_tokens": 120433,
  "by_upstream": [
    { "upstream_model": "claude-opus-5-5", "events": 130, "cost_micro_usd": 1801000 },
    { "upstream_model": "claude-haiku-5-5", "events": 12, "cost_micro_usd": 33200 }
  ],
  "window": { "from": "2026-09-22T09:00:00.000Z", "to": "2026-09-23T09:00:00.000Z", "session_id": null }
}
```

`tasks` is the number of billable task starts in the window. `session_id` matches either the `X-Codai-Session-Id` you sent or the key the gateway derived for header-less clients. Token counts are the **real** upstream figures — parsed from `message_delta.usage` on Anthropic streams and from the final usage chunk on OpenAI streams — not the headline number a client might display.

Prices per upstream model are shown in the hub next to each usage row; the gateway has no separate pricing endpoint.

## Error contract [#error-contract]

Errors use the OpenAI envelope on every surface, so an OpenAI-compatible client parses them unchanged:

```json
{
  "error": {
    "message": "You're sending requests a little too fast — the limit is 100 requests per minute …",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded"
  }
}
```

`code` is stable and machine-readable; `message` is for humans and may change. Validation failures add a `details` object with the field paths that failed. Upstream provider names are scrubbed from messages — you see what failed, never which cloud served it.

| HTTP | `code` | `type` | When |
| --- | --- | --- | --- |
| 400 | `bad_request` | `invalid_request_error` | Invalid JSON, schema violation (see `details`), unsupported Responses feature, `X-Codai-No-Task` on a non-utility alias, unknown audio or image model, media `response_format` other than `b64_json` or `url`. |
| 400 | `content_filtered` | `invalid_request_error` | The image safety filter blocked every generated image. Not charged. |
| 401 | `invalid_api_key` | `invalid_request_error` | Missing, malformed, revoked or unknown key; ephemeral token on the wrong surface. |
| 402 | `subscription_inactive` | `subscription_required` | The key's subscription is not active. |
| 403 | `forbidden` | `invalid_request_error` | Model not in the key's allowlist; agent mode without a tools-enabled tier; ephemeral token trying to mint tokens. |
| 404 | `not_found` | `invalid_request_error` | Unknown route or method. Check the base URL — `/v1` vs root is the usual cause. |
| 413 | `payload_too_large` | `invalid_request_error` | Body over 12 MB. |
| 429 | `rate_limit_exceeded` | `rate_limit_error` | Request, input-token or output-token window exhausted; daily or monthly spend cap reached. Has `Retry-After`. |
| 429 | `quota_exceeded` | `rate_limit_error` | Plan task allowance used up. Has `Retry-After` pointing at the reset. |
| 502 | `upstream_error` | `upstream_error` | The upstream provider failed after the gateway's own retries and failover. Safe to retry. |
| 500 | `internal_error` | `server_error` | Gateway fault. Reported automatically; safe to retry. |
| 503 | `assets_unavailable` | `invalid_request_error` | `response_format: "url"` or `/v1/assets` on a gateway without asset storage. Use `b64_json`. |

Mid-stream failures cannot change the status code (headers are already sent); the gateway closes the stream with a clean stop frame and records the turn as stalled. Retry the request — prompt caching makes it cheap.

### What is recorded on failure [#what-is-recorded-on-failure]

Failed requests are recorded too, with the error code and `is_task_start = 0` — a failed attempt never bills a task, and any task consumption reserved before dispatch is refunded.
