# Limits and quotas

Source: https://docs.usetone.ai/limits

> Two budgets that fail in different ways, and need different responses.

```bash
curl "$TONE_API/v1/limits" -H "Authorization: Bearer $TONE_KEY"
```

```json
{
  "data": {
    "environment": "live",
    "requests": { "limitPerMinute": 600, "remaining": 594 },
    "concurrency": {
      "agentCalls": { "inUse": 2, "limit": 10 },
      "byoCalls":   { "inUse": 0, "limit": 10 }
    }
  }
}
```

Live and test have **separate budgets**, and this answers for whichever key you
asked with.

## Requests per minute

A per-organization token bucket. Every keyed response carries the headers, on
successes too — a client that only reads them on a `429` has already been
throttled:

```
RateLimit-Policy: "org";q=600;w=60
RateLimit: "org";r=594;t=41
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 594
X-RateLimit-Reset: 1756029667
```

Exceeding it is `429 rate_limited` with a `Retry-After`. **Slow down and retry.**

Dashboard traffic does not spend this budget, so a busy browser tab cannot
throttle your integration.

## Concurrent calls

Two pools that do not borrow from each other:

| Pool | |
|---|---|
| `agentCalls` | Calls running a Tone agent |
| `byoCalls` | Calls bridged to your own media stack |

Exceeding one is `429 concurrent_call_limit_reached` — a **different failure**
that slowing down does not fix. You have to wait for calls to end.

🔴 A campaign's `maxConcurrent` **does not queue** above your quota. Recipients
dialled over it are refused and settle as `failed`, which looks like bad phone
numbers rather than a configuration mistake. Read `GET /v1/limits` and keep
`maxConcurrent` at or below what it reports.

⚠️ Inbound and outbound currently share the agent pool. A campaign at your quota
can lock out your published line.

## Handling both

```js
if (res.status === 429) {
  const { error } = await res.json();
  if (error.code === 'rate_limited') {
    await sleep(Number(res.headers.get('retry-after')) * 1000);
    return retry();                 // submitting too fast
  }
  if (error.code === 'concurrent_call_limit_reached') {
    return queueForLater();         // too many calls live — waiting won't help soon
  }
}
```

Both are `429`, and treating them the same means either hammering a full
concurrency pool or needlessly stalling on a rate limit. Branch on `error.code`.

Need more? Both are raised per organization — ask.
