Two different things can slow a Wapito request down, and they answer differently. Plan limits
are the quotas you bought: how many requests a minute and a month, how many messages a day, how
many number checks. Anti-ban controls are the limits the linked number needs whatever you
bought: a send queue that paces messages, a warm-up ladder for new numbers, a guard on messages to
strangers, and WhatsApp's own reach-out timelock. Both answer 429, but with different codes, and
the right reaction is different for each. This page lists them all.
Plan limits
| Limit | Sandbox | Premium |
|---|---|---|
| Channels | 1 | Unlimited, billed per channel |
| API requests per minute, per channel | 60 | 300 |
| API requests per month | 1,000 | Unlimited |
| Messages sent per day | 150 | Unlimited (the warm-up ladder still applies) |
| Number checks per day | 30 | 2,000 |
| Webhook endpoints | 2 | 5 |
| Maximum media size | 16 MB | 64 MB |
| Dedicated proxy | — | Optional |
The prices and the rest of the plan are on the pricing page. Three counters do the counting:
- Requests — every call to
/v1/*counts once toward the month, whatever it does. The per-minute limit is a fixed one-minute window per channel, so two channels in one account have two windows. - Sent — one per message accepted by a send endpoint (
/messages/*,/stories/*, a message to a Channel). A send that is rejected — a bad recipient, a quota refusal, a timelock — is not counted, and one the engine refuses after acceptance is refunded. - Checks — one per phone number looked up. A bulk
POST /contacts/checkof fifty numbers costs fifty, whether or not they exist on WhatsApp.
Days and months are UTC: the daily counters reset at 00:00 UTC and the monthly one on the first of the month. The dashboard's Usage tab draws the last thirty days of each.
Reading what is left
Every response carries the per-minute state of the window it was counted in:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1789459260
X-RateLimit-Reset is the epoch second at which the window rolls over. For everything else — the
daily and monthly quotas, the warm-up rung, the cold-send budget and the timelock — ask
GET /channel/limits before a bulk
run, and size the batch to the answer instead of discovering the cap through refusals:
{
"plan": "premium",
"rate_limit_per_minute": 300,
"api_requests": { "cap": null, "used": 19422, "resets_at": "2026-10-01T00:00:00.000Z" },
"sent": { "cap": null, "used": 412, "resets_at": "2026-09-16T00:00:00.000Z" },
"number_checks": { "cap": 2000, "used": 38, "resets_at": "2026-09-16T00:00:00.000Z" },
"warmup": { "day": 11, "cap": null, "ladder_complete": true },
"cold_send": { "window_cap": 60, "used": 4, "resets_at": "2026-09-15T09:00:00.000Z" },
"timelock": { "active": false, "reason": null, "until": null },
"media_max_bytes": 67108864,
"webhooks_max": 5
}
cap: null means the plan does not cap that dimension.
The 429 responses
All of them use the standard error envelope. Branch on code; the message is for humans.
code | Which limit | What to do |
|---|---|---|
rate_limited | Requests per minute. | Wait for the Retry-After header (seconds), then retry the same request. |
quota_exceeded | A daily or monthly quota; details.quota names it, with used and resets_at. | Wait for resets_at, or upgrade. Nothing to retry sooner. |
send_rate_limited | The channel's send queue is saturated. | You are pushing sends faster than the queue drains; back off for a few seconds and slow the producer. |
warmup_limit | Today's warm-up rung; details carries day, cap, used. | Stop sending for the day. The rung rises tomorrow. |
cold_send_limit | This hour's budget of first messages to strangers. | Send only to people who have written to you until the hour resets; see below. |
And one that is not a 429 but belongs in the same handler: 423 reachout_timelocked, with
details.until, means WhatsApp itself has paused new reach-outs from this number. Replies still
go through; cold sends do not until until.
{
"error": {
"code": "quota_exceeded",
"message": "The plan quota for this resource is exhausted.",
"details": { "quota": "sent", "used": 150, "resets_at": "2026-09-16T00:00:00.000Z" },
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}
A client that treats every 429 as "retry with exponential back-off" will hammer a daily quota
uselessly for hours. The retryable one is rate_limited, and it tells you exactly how long to
wait:
# --fail-with-body keeps the JSON error; -D - prints the headers so Retry-After is visible.
curl --fail-with-body -D - https://api.wapito.com/v1/messages/text \
-H "Authorization: Bearer $WAPITO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "+15551234567", "body": "Your table is ready."}'
The send queue
Every channel has one first-in, first-out send queue. Messages leave it one at a time, spaced by
the channel's send_delay_ms — 1,500 ms by default, and no lower than 500 ms on Sandbox — plus a
random jitter of up to 1,500 ms, so a burst of a hundred API calls becomes a hundred sends over a
few minutes that look like a person typing. If a call would have to wait more than twenty seconds
in the queue it is refused with send_rate_limited instead of being held open, which is the
signal that your producer is running ahead of the number.
Typing simulation rides on the same queue: a typing_time of up to fifteen seconds on a text
send, or typing_simulation: "auto" in the channel settings, shows the recipient "typing…" for a
duration proportional to the message before it is sent. These are anti-ban controls, not plan
features, and they are the same on every plan — see the anti-ban guide.
The warm-up ladder
A number that has just been linked is on day 0 of a ladder that caps how many messages it may send each day, before the plan cap is even considered:
| Day since first connection | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + |
|---|---|---|---|---|---|---|---|---|
| Sends allowed that day | 50 | 100 | 200 | 350 | 500 | 750 | 1,000 | plan cap |
The clock starts the first time the phone number connects and is not reset by a disconnect, a
reconnect or a change of engine. It is reset when a different phone number is linked to the
same channel — the ladder belongs to the number, because it is the number WhatsApp is judging. A
send past the rung answers 429 warmup_limit; on Sandbox the plan's 150 a day is the lower of the
two from day 2 on, and that case answers quota_exceeded instead, so the code always names the
limit that actually stopped you.
The cold-send guard
A recipient is cold when they have never messaged the number, or when your first message to them went out less than 24 hours ago and they have not replied since. Groups and Channels are never cold. Each hour the channel may start only so many conversations with cold recipients:
| Warm-up day | Cold reach-outs per hour |
|---|---|
| 0 – 1 | 10 |
| 2 – 6 | 20 |
| 7 + | 60 on Premium, 20 on Sandbox |
What is counted is the number of new cold recipients contacted in the last hour, not the
number of messages: a follow-up to a stranger you wrote to twenty minutes ago spends no second
slot. What is blocked once the budget is spent is every cold send, follow-ups included — replies
to people who have written to you keep flowing. The refusal is 429 cold_send_limit, and a
refused send is not counted anywhere.
This is the control most likely to surprise a team migrating from a library that had no such thing. It exists because messages to people who never asked for them are what gets a number reported, and reports are what get it banned. The honest guide to avoiding bans explains the numbers behind the numbers.
The timelock
WhatsApp sometimes puts a number into a state where it may reply but may not start new
conversations for a while. Wapito detects that from the engine and reports it in
GET /channel/limits under timelock; while it is active, a cold send answers
423 reachout_timelocked with details.until, and replies go through unchanged. Treat it as a
warning shot: the number has been doing something WhatsApp did not like, and the right response
is to lower the volume, not to route around the lock.