Message allowances
Every plan includes a number of outbound messages per billing cycle. At a hundred percent, outbound sending stops and everything else keeps working.
What counts
Outbound only. A message counts when you send it: one
message to one recipient is one message, whether it started from
POST /v1/messages, the shared inbox, an auto-reply or a campaign. A
message to a group counts once.
Three things do not count:
- Inbound messages. However many arrive, they are free. A busy month of customers writing to you costs nothing.
- Test key sends. A
sk_test_key validates every request, stores every message and fires real webhooks, and delivers nothing to WhatsApp - so it spends no allowance. Build against it freely. - Failed messages. A message that never reached anybody has not spent anything. This means the count can go down as failures land, which is why nothing stores it as a running total.
The allowances
| Plan | Outbound messages per cycle |
|---|---|
| Starter | 3,000 |
| Growth | 7,500 |
| Scale | 20,000 |
| Team | 40,000 |
| Business | 60,000 |
| Verified | 80,000 |
| Volume | 100,000 |
| High Volume | 500,000 |
| Enterprise | 1,000,000 |
| Uncapped | 3,000,000 (fair use) |
A trial is capped by the day rather than the cycle: 100 outbound messages a day, whichever plan is being tried. Everything else about that plan applies in full.
The cycle
The allowance resets on the account's own renewal day rather than on the first of the month, and it does not roll over. A cycle day past the end of a short month lands on the last day of it, so an account anchored on the 31st resets on the 28th in February.
Moving up a plan applies immediately, including to the cycle you are in.
What happens at the limit
At eighty percent, and again at a hundred, we tell the account in the console and by email. At a hundred percent, a send is refused:
HTTP/1.1 402 Payment Required
{
"error": {
"type": "invalid_request",
"code": "message_allowance_exhausted",
"message": "The Growth plan covers 5,000 outbound messages a cycle, and this account has used all of them. Inbound messages and Desk keep working. Move up a plan under Billing in the console, or wait for the cycle to reset."
}
}402, not 429, and the difference matters to your code. A
rate_limited 429 passes on its own and should be retried after the
Retry-After header. This will not pass on its own: retrying with a
backoff will simply fail for the rest of the cycle. Treat it the way you would
treat a declined card - surface it, and stop.
Nothing else stops. Inbound messages still arrive, webhooks still fire, conversations still load and the shared inbox still works, so anybody mid-conversation with the account can still reach it.
Reading the allowance yourself
GET /v1/usage carries it, counted by the same code that refuses a
send - so the figure you read and the figure that refuses you cannot disagree.
curl https://njiwa.upeo.ai/v1/usage \
-H "Authorization: Bearer sk_live_..."{
"messages": {
"used": 4210,
"allowance": 5000,
"remaining": 790,
"percent": 84,
"exhausted": false,
"window": "cycle",
"resets_on": "2026-09-20",
"plan_name": "Growth",
"counts": "outbound"
}
}window is cycle on a plan and day on a
trial. Check it before telling anybody what their monthly allowance is: a trial
does not have one.
Going over
There is no overage charge on any plan. The ceiling is a stop rather than a meter, and no credit is extended. If metered overage arrives later, it will be announced before it starts and carry a limit the account sets itself.