Reference
Rate Limits
Sendexa enforces per-endpoint and global rate limits to ensure fair usage. Limits are applied per API key.
Limits are per API key
All limits below apply per API key per minute. If you need higher limits, contact support@sendexa.co.
Limits by Endpoint
| Channel | Endpoint | Req / min | Burst |
|---|---|---|---|
| SMS — single send | POST /v1/sms/send | 120 | 20 |
| SMS — bulk send | POST /v1/sms/bulk | 20 | 5 |
| OTP — request | POST /v1/otp/request | 60 | 10 |
| OTP — verify | POST /v1/otp/verify | 120 | 20 |
| WhatsApp — send | POST /v1/whatsapp/send | 60 | 10 |
| Email — single send | POST /v1/email/send | 120 | 20 |
| Email — bulk send | POST /v1/email/bulk | 20 | 5 |
| Voice — call / TTS | POST /v1/voice/* | 30 | 5 |
| Status lookups | GET /v1/*/status/:id | 300 | 50 |
| Global (all routes) | All endpoints | 500 | — |
Burst — the maximum number of requests allowed in a single second before token-bucket smoothing kicks in.
Rate Limit Headers
Every API response includes these headers so you can track your current usage window.
Bash
HTTP/1.1 200 OKX-RateLimit-Limit: 120X-RateLimit-Remaining: 87X-RateLimit-Reset: 1716038460
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed in the current window. |
| X-RateLimit-Remaining | Requests remaining before the limit resets. |
| X-RateLimit-Reset | Unix timestamp (seconds) when the window resets. |
| Retry-After | Seconds to wait before retrying — present only on 429 responses. |
429 — Too Many Requests
When you exceed a limit, the API returns a 429 with a body explaining the violation.
JSON
HTTP/1.1 429 Too Many RequestsRetry-After: 14{"success": false,"message": "Rate limit exceeded. Try again in 14 seconds.","code": "RATE_LIMIT_EXCEEDED","requestId": "req_abc123"}
Handling Rate Limits
Use exponential backoff with jitter when you receive a 429. Always respect the Retry-After header rather than blindly retrying.
TypeScript
async function sendWithRetry(payload: SendSmsRequest, maxRetries = 3) {for (let attempt = 0; attempt <= maxRetries; attempt++) {try {return await client.sms.send(payload);} catch (err) {if (err instanceof SendexaError && err.status === 429) {if (attempt === maxRetries) throw err;// Respect Retry-After if present, otherwise exponential backoffconst retryAfter = err.retryAfter ?? Math.pow(2, attempt);const jitter = Math.random() * 1000;await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));continue;}throw err; // non-rate-limit errors bubble up immediately}}}
- For bulk sends, spread requests over time rather than firing all at once.
- Cache status lookups — poll at 5-second intervals rather than hammering continuously.
- Use webhooks for delivery updates instead of polling the status endpoint.