Rate limits & caps
Dialer Digital brakes in more than one place, and the code of the standard error envelope always names which brake you hit. This page walks through the ones you are most likely to design around; it is not a closed census of every 429 the API can return. None of them sets a Retry-After header, so your client owns its backoff.
The rule that survives new codes
Retry any 429 with backoff and jitter — except the spend caps, which retrying cannot clear. Branch on that distinction rather than on a list of codes: a client that enumerates today's codes stops backing off correctly the moment a new one ships.
429 throttled — rate brakes
Operations that reach the telephony or SMS pipeline are throttled when they arrive faster than the pipeline will take them:
{
"error": {
"code": "throttled",
"message": "dial throttled (rate)"
}
}- Where:
POST /v1/campaigns/{id}/dial(manual/preview dials) andPOST /v1/sms/messages. - Retryable: yes. Back off exponentially with jitter (e.g. 1 s → 2 s → 4 s …, capped) and retry the same request.
- For SMS, retries must reuse the same
client_ref— it is the idempotency key, and a replayedclient_refis acknowledged without contacting the provider twice. Never generate a freshclient_reffor a retry.
429 rate_limited — request-rate brakes on agent actions
{
"error": {
"code": "rate_limited",
"message": "call control rate limit exceeded; retry later"
}
}Call-control verbs and dispositions carry their own request-rate brake. Unlike throttled, which is about the telephony pipeline's appetite, this one bounds how fast a single principal can act:
- The budget is per most specific durable principal — agent, else user, else tenant — never per tenant when a narrower one exists, so one runaway page cannot throttle its sibling agents. Durable is the load-bearing word: the budget survives re-authentication, so logging back in mints a new token but does not buy a fresh budget.
- The four call-control toggles share one budget.
hangupis exempt and never answers429— refusing a teardown would leave a call alive that its operator asked to end. - Dispositions have their own, narrower budget. What it bounds is durable row growth: every accepted disposition inserts a correction row into the append-only attempt history.
- Refusal happens before the lookup, so the response is identical whether or not the
call_idor attemptidexists — a throttled client cannot use it to probe for what exists.
Retrying in the next window is the correct response. There is no Retry-After.
Concurrency ceilings — also 429, also retryable
Three more 429s are not rate brakes at all: they say "this resource is busy right now", and each one tells you exactly what has to happen before a retry succeeds.
code | Where | Retry when |
|---|---|---|
export_in_progress | Recording/CDR export | The in-flight export for your tenant finishes. An identical repeat then takes the reuse path and answers from the archive it produced. |
supervision_capacity | Starting supervision of a call | A supervised call ends. This is the tenant's supervision quota — its own ceiling, never the dialing one. |
too_many_connections | The /v1/ws handshake | A live-floor connection frees up. Bounded per widget token and per tenant. |
They are covered by the rule at the top: back off and retry. Do not treat them as fatal, and do not reconnect in a tight loop against too_many_connections.
429 sms_spend_cap_exceeded — the monthly SMS budget
{
"error": {
"code": "sms_spend_cap_exceeded",
"message": "month-to-date SMS spend is at the tenant cap"
}
}Your account has a month-to-date SMS spend cap (sms_monthly_budget_usd on GET /v1/me). When month-to-date spend reaches it, sends stop before any provider is contacted — no message goes out, nothing is billed.
- Retrying does not help until the cap is raised or the calendar month rolls over. Treat it as a budget event, not a transient error: alert a human.
- Caps are set with your account manager (they live on the admin surface, so a leaked API key cannot raise your own budget).
Why caps exist
Autodialers and SMS blasters are spend amplifiers — a bad loop or a bad list can burn a month's budget in an hour. The caps are a cost-integrity feature: the platform enforces the ceiling upstream of the money, the same fail-closed way it enforces compliance. The caps on your account today:
| Cap | Where you see it | What it stops |
|---|---|---|
sms_monthly_budget_usd | GET /v1/me | SMS sends beyond the monthly budget (429 sms_spend_cap_exceeded). |
ai_monthly_budget_usd | GET /v1/me | Voice-AI minutes beyond the monthly budget. |
ai_max_call_seconds | GET /v1/me | Any single AI call running away. |
Month-to-date usage is queryable at GET /v1/stats/sms-usage and GET /v1/stats/ai-usage — poll them if you want your own alerts before the brake engages.
Request-level limits
| Limit | Value | Behavior at the limit |
|---|---|---|
| Request body size | 8 MiB | Rejected. Split large imports into batches. |
| Webhook body size | 1 MB | 413. |
Page size (limit param) | max 1000, default 100 | Values above the max are rejected as bad_request. |
Being a good citizen
- Page with cursors (
limit+next_cursor) instead of huge limits. - Prefer the WebSocket live floor over polling for realtime state (agent presence, call lifecycle, pacing).
- Batch lead imports (
POST /v1/debts/importtakes arrays and is idempotent) rather than importing one debt per request. - Backoff with jitter on every
429exceptsms_spend_cap_exceeded, and on every5xx. Branch on the code being a spend cap, not on a list you have to keep in sync — see the rule at the top of this page.