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
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.