Skip to content

Rate limits

A rate limit is the number of requests you may make in a period of time. SanadWA limits requests per API key, per minute.

  • The default is 60 requests per minute for each API key.
  • An admin can set a different limit for a key, from 1 to 3600 requests per minute. Set it when you create the key (Rate limit (req/min)), or later with Edit in Settings → Developers → API keys.
  • Every request with a valid key counts, including GET requests and requests that end in an error.
  • Requests rejected because the key is missing, wrong, blocked by the IP allowlist or lacks the scope are rejected before the limit is checked.

POST /campaigns has a second, lower limit: 5 requests per minute for each API key. It counts separately from the general limit, and every request to POST /campaigns counts toward both, including retries that return an earlier answer. Above it you get 429 rate_limited with Retry-After, and this message:

{
"success": false,
"code": "rate_limited",
"message": "This action is limited to 5 requests per minute for each API key. Retry in 40s."
}

This limit cannot be changed per key. It exists so that a script stuck in a loop cannot start many campaigns.

This limit is separate from WhatsApp’s own limits on how many messages a number may send. If WhatsApp refuses a message because of its limits, you get a send_failed error, not rate_limited.

Responses to requests with a valid key include these headers:

Header Meaning
X-RateLimit-Limit Your limit, in requests per minute.
X-RateLimit-Remaining How many more requests you can make before you are limited.
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47

These headers describe the general limit only, not the campaign limit.

Use X-RateLimit-Remaining to slow down before you reach the limit. There is no header that says when the limit resets.

You get HTTP status 429 and this body:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{
"success": false,
"code": "rate_limited",
"message": "Rate limit exceeded for this API key. Retry in 12s."
}

Retry-After is the number of seconds to wait before the next request. It is at least 1.

Do not count on the allowance coming back bit by bit during the minute. When you reach the limit, wait the full Retry-After time before you send more requests.

A 429 means the request was not processed. No message was sent. It is safe to send the same request again after the wait, with the same idempotency key.

  1. When you get 429, read Retry-After and wait that many seconds.
  2. Then retry the same request.
  3. If you get 429 again, wait longer each time (for example double the wait), and add a small random delay so that many workers do not retry at the same moment.
  4. To avoid 429 in the first place, spread your sends over time. A queue in your system that sends at most your limit per minute works well.
async function callWithBackoff(doRequest, maxAttempts = 5) {
for (let attempt = 1; ; attempt++) {
const res = await doRequest();
if (res.status !== 429 || attempt === maxAttempts) return res;
const retryAfter = Number(res.headers.get("Retry-After")) || 1;
const wait = retryAfter * 1000 * 2 ** (attempt - 1) + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
}

If your normal traffic needs more than 60 requests per minute, ask an admin of your account to raise the limit of that key.