Skip to content

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_EXCEEDED responses also include X-RateLimit-Limit and X-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 if Retry-After is 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/send still 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 a 429 before that ceiling is reached. A completed idempotent replay does not create another SMS or consume that dedicated live budget.
  • Each recipient in /sms/send and each message in /sms/send-individual consumes 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, and X-RateLimit-Burst: 0; contact support instead of retrying. Application per-IP 429 responses carry the same rate-limit headers.
attempt = 0
while 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-After when present.
  • Spread batch traffic — for sustained high volume, a steady stream beats bursts that bounce off the limiter.