Limits and quotas
Two budgets that fail in different ways, and need different responses.
curl "$TONE_API/v1/limits" -H "Authorization: Bearer $TONE_KEY"{
"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: 1756029667Exceeding 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
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.