Rate limits & quotas
Four things can refuse a valid request. They look alike and are fixed in four different places, so
the code matters more than the status.
| Code | Status | What ran out | How it clears |
|---|---|---|---|
rate_limited | 429 | This key's requests per minute | Seconds |
key_limit_reached | 403 | This key's monthly messages | Raise the key's limit |
quota_exceeded | 403 | Your plan's allowance | Upgrade, or free something up |
spend_cap_reached | 403 | This key's Meta spend ceiling | The billing cycle turning |
Only the first is worth retrying. The other three do not refill by waiting — each needs a limit raised, a plan changed, or the billing cycle to turn.
Reading your rate limit
When a key has a per-minute limit, every response carries it:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1The SDK records it on the client:
Every field is null on a key with no limit set. The headers are absent in that case, so treat them
as optional.
Handling a limit
rate_limited carries Retry-After in whole seconds, and the SDK already retries it — three
attempts, backing off, honouring the header. You usually do not need to write anything.
If you are running a batch, pace it off msg.rateLimit.remaining instead of retrying into the wall.
Warnings before the wall
A webhook fires at 80% and 100% of a key's monthly message limit, so you can react before a customer is cut off. There is no equivalent for the spend cap — the refusal is the only signal there.