Skip to main content

Rate limits

:::info No rate limiting is enforced today There is no request quota on this API, no 429 in normal operation, and no rate-limit headers on responses. The per-key call-limit fields visible in the admin center are not applied to this API. :::

That is a statement about today, not a promise. Treat it as an implementation detail that will change, and build as though limits exist.

Build like they exist

Throttle your own traffic. An unthrottled loop against GET /users on a large account is a self-inflicted outage, limits or no limits.

Handle 429 now. It costs a few lines and it is in the error catalog already:

429 → { "error": { "type": "rate_limited", "message": "…" } }

Back off exponentially with jitter and retry — nothing happened, so a retry is always safe.

Read Retry-After if it is present. When limits arrive they will use the standard header. A client that already respects it needs no change.

Cheaper than polling

The three patterns that generate the most needless load, and what to do instead:

Instead ofDo this
Polling for new SMSSubscribe to the AccountSMS webhook
Polling for ended callsSubscribe to AccountEndedCalls
Re-fetching GET /users per requestCache it. It changes when staff join or leave, not per call.

Recordings are the honest exception: a recording appears shortly after hangup, and [] means "not yet". Poll that one — but poll it on a delay after the AccountEndedCalls event rather than on a timer.

Timeouts

Give requests a generous client timeout. A send goes through a carrier gateway, and a recording list touches storage. A timeout is not a failure — on POST /sms/messages the message may have been sent and billed. See what is safe to retry.