429, and confusing them wastes a lot of debugging time. Check error.code first:
rate_limited
You’re calling the API too fast. 60 requests per minute, per credential. Back off and carry on.
quota_exceeded
You’ve used up the project’s WhatsApp sends for the day. Slowing down won’t help — it resets at UTC midnight.
The API rate limit
60 requests per minute, counted in a fixed one-minute window. The limit is flat — it doesn’t vary by plan or by endpoint. Because the window is fixed rather than sliding, the counter resets outright at the boundary —RateLimit-Reset tells you exactly when. Bursting right before a reset and again right after is the one way to briefly exceed 60 in any given 60 seconds.
It’s counted per credential, not per project or per IP. Each API key gets its own 60, and each connected AI agent gets its own. Two integrations on the same project don’t eat each other’s budget; two copies of the same integration sharing one key do.
The headers
Every rate-limited response carries these, not just the ones that fail:
On a
429 you also get:
Handling it
quota_exceeded in a tight loop just burns requests against the rate limit for no benefit.
The throttle fails open. If the counter store has a blip, requests are allowed through rather than rejected — a hiccup in Paige’s infrastructure will never turn into a wall of
429s for you. Don’t design around it; just know that a brief window of no limiting isn’t a bug.The daily WhatsApp send quota
This is a completely separate control, and it only applies toPOST /v1/messages.
Every project has a daily allowance of WhatsApp messages, defaulting to 1,000 per project. It’s counted per UTC calendar day and resets at UTC midnight. It exists so a runaway loop can’t send thousands of messages to your customers — and, because WhatsApp sends cost money, can’t quietly run up a bill.
When it’s used up:
details.limit is your project’s actual allowance and details.resetAt is exactly when it lifts. A Retry-After header carries the same wait in seconds.
Broadcasts are the right tool for high-volume sends. They’re paced for you and don’t require you to build your own throttling. See Broadcasts.
Designing to stay under both
- Batch reads.
GET /v1/conversationswithlimit=100is one request; a hundred single-conversation lookups is a hundred. - Use webhooks instead of polling. A poll every few seconds burns your budget continuously and still tells you about events late. Webhooks push them the moment they happen.
- Give each integration its own key. Separate budgets, and separate scopes, so one busy job can’t starve another.
- Never retry into a
429without waiting. HonourRetry-Afterevery time.
