Reference

Rate Limits

Sendexa enforces per-endpoint and global rate limits to ensure fair usage. Limits are applied per API key.

Limits by Endpoint
ChannelEndpointReq / minBurst
SMS — single sendPOST /v1/sms/send12020
SMS — bulk sendPOST /v1/sms/bulk205
OTP — requestPOST /v1/otp/request6010
OTP — verifyPOST /v1/otp/verify12020
WhatsApp — sendPOST /v1/whatsapp/send6010
Email — single sendPOST /v1/email/send12020
Email — bulk sendPOST /v1/email/bulk205
Voice — call / TTSPOST /v1/voice/*305
Status lookupsGET /v1/*/status/:id30050
Global (all routes)All endpoints500

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 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1716038460
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window.
X-RateLimit-RemainingRequests remaining before the limit resets.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.
Retry-AfterSeconds 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 Requests
Retry-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 backoff
const 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.