Skip to content

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:

json
{
  "error": {
    "code": "throttled",
    "message": "dial throttled (rate)"
  }
}
  • Where: POST /v1/campaigns/{id}/dial (manual/preview dials) and POST /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 replayed client_ref is acknowledged without contacting the provider twice. Never generate a fresh client_ref for a retry.

429 rate_limited — request-rate brakes on agent actions

json
{
  "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. hangup is exempt and never answers 429 — 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_id or attempt id exists — 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.

codeWhereRetry when
export_in_progressRecording/CDR exportThe in-flight export for your tenant finishes. An identical repeat then takes the reuse path and answers from the archive it produced.
supervision_capacityStarting supervision of a callA supervised call ends. This is the tenant's supervision quota — its own ceiling, never the dialing one.
too_many_connectionsThe /v1/ws handshakeA 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

json
{
  "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:

CapWhere you see itWhat it stops
sms_monthly_budget_usdGET /v1/meSMS sends beyond the monthly budget (429 sms_spend_cap_exceeded).
ai_monthly_budget_usdGET /v1/meVoice-AI minutes beyond the monthly budget.
ai_max_call_secondsGET /v1/meAny 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

LimitValueBehavior at the limit
Request body size8 MiBRejected. Split large imports into batches.
Webhook body size1 MB413.
Page size (limit param)max 1000, default 100Values 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/import takes arrays and is idempotent) rather than importing one debt per request.
  • Backoff with jitter on every 429 except sms_spend_cap_exceeded, and on every 5xx. 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.

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.