Skip to content

Rate Limiting

Per-project request limits on the Zavu API: the 600 requests per minute default, how it rises with verification, 429 responses, Retry-After, and how it differs from sending limits

View .md

Rate Limiting

Every authenticated request to a /v1/* or /v2/* endpoint counts against a per-project request limit. Requests to both versions share one window. It protects the platform from bursts; it is not a sending quota. The daily ceilings per channel, the per-recipient SMS cooldown and the monthly allowances are separate rules with separate error codes. They are covered in Sending limits and summarized at the end of this page.

Request Limits

LimitWho gets itHow it is set
600 requests/minEvery projectThe default
1,200 requests/minEvery project in a team whose Business Verification (KYB) is approvedApplied automatically when the verification is approved
2,400 requests/minEvery project in a team whose 10DLC brand has been verified by the registryApplied automatically when the brand is verified

A limit that Zavu set for your account by hand is never lowered by these automatic steps, and neither is a limit that is already higher.

How to Increase Your Limit

  1. Complete Business Verification (KYB)

    In the dashboard, go to Compliance and open Business verification. When it is approved, every project in the team moves to 1,200 requests/min.

  2. Register a 10DLC brand (US SMS)

    Register your brand for 10DLC. When the registry verifies it, every project in the team moves to 2,400 requests/min. The campaign is not what raises the request limit; it is what lets you send SMS to US numbers.

Rate Limit Headers

Every /v1/* response includes headers to help you track your usage:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests per minute for this project600
X-RateLimit-RemainingRequests left in the current window542
X-RateLimit-ResetUnix timestamp (ms) when the current window ends1701234600000
Retry-AfterSeconds to wait (only on 429)45

Checking Rate Limits with the SDK

Call .withResponse() on the request to get the parsed result together with the raw HTTP response:

import Zavudev from '@zavudev/sdk'; const zavu = new Zavudev({ apiKey: process.env['ZAVUDEV_API_KEY'], }); const { data, response } = await zavu.messages .send({ to: "+14155551234", text: "Hello from Zavu!" }) .withResponse(); const limit = response.headers.get("X-RateLimit-Limit"); const remaining = response.headers.get("X-RateLimit-Remaining"); const reset = response.headers.get("X-RateLimit-Reset"); console.log(`Rate limit: ${remaining}/${limit} requests remaining`); console.log(`Window ends at: ${new Date(Number(reset)).toISOString()}`); console.log("Message ID:", data.message.id);

Rate Limit Exceeded (429)

When a project exceeds its limit, the API returns 429 Too Many Requests with a Retry-After header (in seconds) and this body:

JSON
{ "code": "rate_limited", "message": "Rate limit exceeded. Retry after 2024-01-15T12:30:00.000Z", "details": { "retryAfter": 1705322400000 } }

details.retryAfter is the same instant as X-RateLimit-Reset, in Unix milliseconds.

If the rate limiter itself cannot be reached, requests are refused with this same response and a 60-second Retry-After rather than let through unchecked.

Handling Rate Limits

The TypeScript SDK retries a 429 on its own, up to 2 times by default, waiting for the Retry-After the API sent. Set maxRetries higher for bursty traffic; handle the error yourself only once the retries are exhausted. For the other SDKs, check the status on the API error the same way the SDK overview does.

TypeScript
import Zavudev from "@zavudev/sdk"; const zavu = new Zavudev({ apiKey: process.env["ZAVUDEV_API_KEY"], maxRetries: 4, }); async function sendWithRateLimitHandling(to: string, text: string) { try { const { data, response } = await zavu.messages .send({ to, text }) .withResponse(); const remaining = Number(response.headers.get("X-RateLimit-Remaining")); if (remaining < 10) { console.warn(`Low rate limit: ${remaining} requests remaining`); } return data; } catch (error) { if (error instanceof Zavudev.RateLimitError) { // Retries are exhausted. Retry-After is in seconds. const waitMs = Number(error.headers.get("retry-after") ?? 60) * 1000; console.log(`Rate limited. Waiting ${waitMs}ms before retry...`); await new Promise((resolve) => setTimeout(resolve, waitMs)); return sendWithRateLimitHandling(to, text); } throw error; } }

How It Works

The limit is a sliding window over the last 60 seconds: at any moment, a project may have at most its limit of requests in the previous minute. There is no wall-clock reset. Capacity comes back as older requests age out of the window, so a burst that hits the limit at 12:00:30 is not cleared at 12:01:00; it clears as the requests from 12:00:30 onward fall out of the window.

Code
Limit: 600 in any 60-second window ├── 12:00:00 request 1 OK (remaining: 599) ├── 12:00:20 request 600 OK (remaining: 0) ├── 12:00:21 request 601 429 rate_limited └── 12:01:00 request 1 has aged out: one slot is back

X-RateLimit-Remaining tells you how many requests fit right now; waiting a full 60 seconds always empties the window.

Other 429 responses that are not rate limits

POST /v1/messages and POST /v1/calls also answer 429 for limits on what you send, not on how fast you call the API. They are different rules with different codes, and none of them is raised by the request limit above.

CodeWhat it meansBody shape
daily_limit_exceededThe channel group's daily ceiling for your account level is spent. See Sending limits{ code, message, details: { limit, remaining, dashboardUrl } }
rate_limit_exceededOne SMS per recipient per minute: a second sms or sms_oneway message to the same number within 60 seconds. The message says how many seconds to wait{ error: { code, message } }
a2p_limit_exceededThe Free plan's monthly allowance of 2,000 messages across WhatsApp, Telegram, Instagram and Messenger is spent, and the balance cannot pay for more (see pay as you go){ error: { code, message, details } }

Best Practices

Monitor Headers

Check X-RateLimit-Remaining to avoid hitting the limit unexpectedly.

Honor Retry-After

Wait the seconds in the Retry-After header, or until details.retryAfter, before retrying.

Use Broadcasts

For bulk sending, use the Broadcasts API instead of individual requests.

Use Idempotency Keys

Include idempotencyKey in requests to safely retry without duplicates.

Bulk Sending Example

For sending many messages from your own loop, pace on the headers:

TypeScript
import Zavudev from '@zavudev/sdk'; const zavu = new Zavudev({ apiKey: process.env['ZAVUDEV_API_KEY'], }); async function sendBulkMessages(messages: { to: string; text: string }[]) { const results = []; let remaining = 600; for (const msg of messages) { // If running low, wait for the window to slide if (remaining < 10) { console.log("Approaching rate limit, waiting 60 seconds..."); await new Promise(resolve => setTimeout(resolve, 60000)); remaining = 600; } const { data, response } = await zavu.messages.send(msg).withResponse(); remaining = Number(response.headers.get("X-RateLimit-Remaining")); results.push(data); } return results; }

Summary

LimitHow to get it
600/minEvery project, by default
1,200/minBusiness Verification (KYB) approved
2,400/min10DLC brand verified by the registry

Next Steps