Reliability
Rate limits and pagination
Send endpoints allow 300 requests per minute per workspace and return 429 rate_limit_exceeded with a retry delay. List endpoints use keyset cursor pagination with a default limit of 50 and a maximum of 100.
The limit
| Surface | Limit | Scope |
|---|---|---|
| POST /v1/*/send and /v1/*/bulk | 300 requests per minute | Per workspace |
The limit counts requests, not recipients. A bulk request carrying a thousand recipients is one request, which is the main reason to prefer /v1/sms/bulk over a loop of single sends for a campaign.
Backing off correctly
On 429, read the delay named in the error's resolution and wait at least that long. Then retry with exponential backoff and jitter, and cap the number of attempts. Pair the retry with the same Idempotency-Key so a duplicate send is impossible.
async function sendBatch(batches: SendPayload[], keyFor: (batch: SendPayload) => string) {
for (const batch of batches) {
let delay = 500;
for (let attempt = 1; attempt <= 5; attempt += 1) {
const response = await fetch("https://api.sendozi.com/v1/sms/bulk", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDOZI_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": keyFor(batch),
},
body: JSON.stringify(batch),
});
const body = await response.json();
if (body.success) break;
if (response.status !== 429 && response.status < 500) {
throw new Error(`${body.error.code}: ${body.error.message}`);
}
// Jitter stops a fleet of workers retrying in lockstep.
await new Promise((resolve) => setTimeout(resolve, delay + Math.random() * 250));
delay *= 2;
}
}
}- Group recipients into bulk requests rather than sending one request per recipient.
- Spread a large campaign over time instead of firing everything in one minute.
- Add jitter, or every worker in your fleet retries at the same instant.
- Do not retry permanent errors - they consume the limit for nothing.
Pagination
GET /v1/messages and GET /v1/message-batches are keyset-paginated on created_at. Pass next_cursor back as cursor and stop when has_more is false. limit defaults to 50 and is capped at 100.
curl "https://api.sendozi.com/v1/messages?limit=100" \
-H "Authorization: Bearer $SENDOZI_API_KEY"{
"success": true,
"data": {
"data": [ ],
"has_more": true,
"next_cursor": "2026-08-21T09:14:22.117Z"
},
"request_id": "req_8ecdcb4ce2ae4290"
}export async function* allMessages() {
let cursor;
do {
const url = new URL("https://api.sendozi.com/v1/messages");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SENDOZI_API_KEY}` },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
yield* body.data.data;
cursor = body.data.has_more ? body.data.next_cursor : undefined;
} while (cursor);
}Frequently asked questions
- What is the Sendozi API rate limit?
- 300 requests per minute per workspace on the send and bulk endpoints. The limit counts requests, not recipients.
- What should I do when I get 429 rate_limit_exceeded?
- Wait at least the delay named in the error's resolution, then retry with exponential backoff and jitter, reusing the same Idempotency-Key so the retry cannot duplicate a send.
- Does a bulk send of 1,000 recipients use 1,000 of my rate limit?
- No. It is one request. That is why bulk is the right endpoint for a campaign.
- How many results can one page return?
- 100 at most; the default is 50. Follow next_cursor until has_more is false.
Related reading
Reliability
Errors and the response envelope
The Sendozi response envelope, every error code with its status and meaning, which errors are worth retrying, and how request_id is used to trace a failure.
Reliability
Idempotency
How Idempotency-Key works on Sendozi send endpoints, what happens on a repeat with the same or a different body, how long keys are retained, and how to choose a key.
Channels
SMS API reference
Endpoints, request fields, routing, batching, sender ID management and page-based cost for sending SMS to Nigerian numbers through the Sendozi API.
Reliability
Delivery webhooks
Register a webhook endpoint, receive message.delivered and message.failed events, verify the signature over the raw body, and build a receiver that survives retries and reordering.