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.

CodeStatusWhat ran outHow it clears
rate_limited429This key's requests per minuteSeconds
key_limit_reached403This key's monthly messagesRaise the key's limit
quota_exceeded403Your plan's allowanceUpgrade, or free something up
spend_cap_reached403This key's Meta spend ceilingThe 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:

Response headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1

The SDK records it on the client:

await msg.templates.list();
msg.rateLimit;   // { limit: 100, remaining: 99, resetSeconds: 1 }

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.