Skip to main content
Every error answers a JSON body with a sentence for a person and a code for a program:

Branch on code, never on the sentence

The status says what kind of problem it is. The code says exactly which. A 403 on POST /calls can mean the customer opted out, the number is on your do-not-call list, or the key is not a full key; those are three different codes, and what you do about each is different (an opted-out customer is never rung again, a do-not-call number can be taken off the list, a key can be swapped for a full one). The sentence in error is for showing to a person and may be reworded in any release. A code does not change; new ones are only ever added. So match on code, and treat a code you do not recognise as the generic one for its status.

Which limit a 429 hit

A 429 names the limit in code and sizes it in a limit object, next to the Retry-After header (seconds to wait):
limit.perMinute is set for the limits counted per minute; limit.max (and limit.windowSeconds when it has a window) for the others, such as concurrent_calls and the codes one number may be sent.

A 402 on POST /calls

code is insufficient_balance. The existing reason field says which limit stopped the call: no balance, trial spent, overdrawn, too many calls, balance unknown or retry buffer.

Statuses

Every code

Generated from the same list the API uses, so it cannot drift from it. A status with no more specific code answers its generic one: validation_failed (400), unauthorized (401), payment_required (402), forbidden (403), not_found (404), conflict (409), gone (410), unprocessable (422), rate_limited (429), internal_error (500) and service_unavailable (502, 503, 504). The key and the workspace it acts in Limits: which one was hit (a limit object names it) Placing a call Agents Numbers Messages and codes Plan and money Idempotency The generic code for a status that has no specific one

On 409 in particular

When Meta restricts an account, every refused request is a signal that the business has not noticed. A retry loop against a restriction extends it.The console shows the reason and when it lifts. Go and look rather than retrying.
409 also covers the correct-behaviour cases: a marketing template addressed to somebody who opted out is refused. That is not a bug to work around.

Retry policy that will not hurt you

The important line is the last one: the same idempotency key on every attempt. Generating a fresh key per retry turns a safe retry into a duplicate message. Keys are honoured for 24 hours, so a backoff that runs for minutes is well inside the window. A job parked overnight and released the next morning is not — it will send.