Rate limits
Copy page
Rate limiting is applied at multiple independent layers. Limits are configurable per account and may change — treat the numbers below as approximate.
| Layer | Scope | Approximate limit | On exceed |
|---|---|---|---|
| Per-IP general (edge proxy) | All requests from one IP, enforced before the application | 10,000 req/s, burst 20,000 | 429 with an HTML body; no Retry-After or X-RateLimit-* |
| Per-IP general (application) | All requests from one IP | Operator-configured | 429 RATE_LIMIT_EXCEEDED + Retry-After: 1 + X-RateLimit-Limit/X-RateLimit-Burst |
| Per-IP auth | Login endpoint per IP | ~5 req/min | 429 TOO_MANY_ATTEMPTS |
| Login brute-force | Per login name, progressive | Progressive delay | 429 TOO_MANY_ATTEMPTS + Retry-After |
| 2FA brute-force | Per user + IP, progressive | Progressive delay | 429 TOO_MANY_ATTEMPTS + Retry-After |
| Per-account | All requests from one account | Default 500 req/s, burst 1,000 unless your account has custom limits | 429 RATE_LIMIT_EXCEEDED + rate-limit headers |
| Strict integration live budget | One enabled integration workspace plus a global safety budget | Self-service default: 100 TPS / 1000 burst per workspace | 429 RATE_LIMIT_EXCEEDED + Retry-After |
| Sandbox daily quota | Recipients per account per UTC day on sk_test_ keys | 10,000 | 429 SANDBOX_QUOTA_EXCEEDED + Retry-After |
| Sandbox capacity | Simulated deliveries awaiting their DLR, all accounts | Server-wide cap | 503 SANDBOX_BUSY + Retry-After: 5 |
Notes worth coding against:
- Login and 2FA brute-force limits and the strict integration live budget send
Retry-After. The application per-IP and per-account limiters also send it. Other 429 responses can omit it; use a fallback delay.RATE_LIMIT_EXCEEDEDresponses also includeX-RateLimit-LimitandX-RateLimit-Burst, which identify the applied request-per-second and burst limits. - The edge proxy returns an HTML 429 without
Retry-After; back off using your own delay. Application 429s are JSON; use a fallback delay ifRetry-Afteris absent. - The per-account limiter is shared across all team members and keys on the account, for both JWT and API-key traffic.
/integrations/sms/sendstill consumes the normal per-account request token. A fresh live claim also passes a separate, enrollment-specific safety budget. A self-service workspace has a 100 TPS / 1000 burst ceiling; custom limits may be lower. The shared global pool is not reserved for one workspace, so other traffic can cause a429before that ceiling is reached. A completed idempotent replay does not create another SMS or consume that dedicated live budget.- Each recipient in
/sms/sendand each message in/sms/send-individualconsumes one token. A batch larger than the burst gets “split it into requests of at most B recipients” and will never succeed by retrying; a temporarily exhausted bucket says “retry after 1 second”. - A suspended account returns 429 with
Retry-After: 60,X-RateLimit-Limit: 0, andX-RateLimit-Burst: 0; contact support instead of retrying. Application per-IP 429 responses carry the same rate-limit headers.
Recommended client behavior
Section titled “Recommended client behavior”attempt = 0while attempt < max_retries: response = send_request() if response.status != 429: break delay = retry_after_header or (base_delay * 2 ** attempt) + jitter sleep(delay) attempt += 1- Exponential backoff with jitter, starting around 1s.
- Honor
Retry-Afterwhen present. - Spread batch traffic — for sustained high volume, a steady stream beats bursts that bounce off the limiter.
