Error handling
Every error response uses one envelope and a stable code. Handle errors by their code, use the retry hint to decide whether to try again, check whether the work may already have started, and log the request_id.
The error envelope
Every non-2xx response has this shape:
JSON
| Field | Type | Meaning |
|---|---|---|
code | string | Stable, machine-readable error code. Branch on this. Codes are never renamed in v1; new codes can be added. |
message | string | A sentence for people, safe to show an operator. It may be reworded at any time: never parse it. |
details | object | Extra facts for this code, such as required_scopes or errors[], documented on each code's page. Always an object; {} when the code has none. |
request_id | string | The request id. Log it and quote it to support. Also log the X-Request-Id header of the response you received: for a replayed answer (Idempotent-Replayed: true), the body can carry the id of the original request. |
retry | string | What to do next: no, yes or after (below). |
doc_url | string | The code's page in the error-code reference, with causes and fixes. |
Each code always comes with the same HTTP status, so you can rely on the status class (4xx, 5xx) as a fallback, but the code is what distinguishes, for example, the several kinds of 409. Error messages never include internal details, and no error is ever returned with a 2xx status.
The retry hint
retry | Meaning | What to do |
|---|---|---|
no | Retrying the same request will fail the same way. | Change the request or the configuration first. |
yes | A temporary problem. | Retry with exponential backoff and jitter. For a POST or DELETE, reuse the same Idempotency-Key if you sent one. |
after | Wait first. | Wait the number of seconds in the Retry-After header, then retry (with the same Idempotency-Key, if you sent one). |
"The same key" on this page means the Idempotency-Key you sent with the original request. It is optional; without one, a retried POST or DELETE runs again, so read the state before retrying a write whose outcome you do not know.
Never retry no errors in a loop. Cap retries for yes and after (for example at five attempts) and give up loudly. A very long Retry-After, such as the hours until the daily call limit resets, is a signal to schedule the work for later, not to sleep.
Did anything happen?
Most errors mean the request was refused before it did anything: nothing was created, dialled or charged. A few can happen after work may have begun, for example when the start of a call or a campaign was sent but its confirmation was lost. Never assume an error means no side effects. Each code's page states which kind it is, as Execution:
| Execution | Meaning |
|---|---|
| Not started | Nothing was done. Fix the cause (or wait) and send the request again. |
| May have started | The work may have happened, partly or fully. Read the resource before doing anything else; with a key, an identical retry with the same key is also safe. |
| Not applicable | Reads, where nothing is changed either way. |
These codes may have started work:
internal_error(500,retry: yes): A500is not stored under anIdempotency-Key: a retry with the same key runs the request again from the start; it does not replay an earlier result. A read can simply be retried. For a write, the API does not know whether the change was applied before the error, so read the resource first (for a call, list calls filtered bytoandcreated_after; for a campaign or a contact, retrieve it) and retry with the same key only if the change is not there.idempotency_key_in_progress(409,retry: after): The first request with this key is still running. WaitRetry-After, then send the identical request with the same key to get its result.idempotency_outcome_unknown(409,retry: no): The first request with this key was interrupted and its outcome is unknown. Read the resource (for a call, list calls bytoandcreated_after). Only if it did not happen, send it again with a new key.campaign_outcome_unknown(409,retry: after): A start or resume was sent but not confirmed, and dialling may have begun. This answer is stored under the key: a retry with the same key returns it again and sends nothing. Wait, then retrieve the campaign:runningmeans it started. Only if it is not running and you still want to try, send the action with a new key; the campaign refuses a second dial of the same run.campaign_pause_not_confirmed(409,retry: yes): The pause was not confirmed and dialling may continue. Retry the pause with the same key, then retrieve the campaign; if it is still running, stop it.
A timeout or a dropped connection, where you get no response at all, is the same situation: the request may or may not have run. See After a timeout.
One more case: POST /v1/calls answers 202 even when the start of the call cannot be confirmed straight away. The call is then resolved within about two minutes: it either proceeds, or ends with outcome: failed and end_reason: not_confirmed, and nothing is charged.
Handling errors by class
- Fix the request: A bug or bad input. Fix it and send again. A refused request is not stored, so an
Idempotency-Keyyou sent may be reused. Examples:validation_error,invalid_json,invalid_phone_number,unsupported_media_type,payload_too_large,invalid_idempotency_key,invalid_cursor. - Fix credentials or access: Stop and alert a person: an admin usually has to change a key or a setting. Examples:
invalid_api_key,api_key_expired,insufficient_scope,insufficient_permissions,api_access_not_enabled,ip_not_allowed. - Fix the configuration: Something in the console needs attention. Alert an operator; retry after it is fixed. Examples:
agent_not_ready,calling_not_configured,phone_number_not_usable,insufficient_balance,spend_limit_reached,account_inactive,campaign_dnc_override_enabled. - Respect a rule: Expected business outcomes, not failures. Record them; do not retry until the situation changes (consent recorded, calling hours open, the next day). Examples:
dnc_listed,consent_required,outside_calling_hours,contact_daily_limit_reached. - Check the id: Check the id, its prefix and the key's mode. A test-mode id is not found with a live key. Examples:
resource_not_found,route_not_found. - Check state first: Read the current state and decide again. For
idempotency_key_reused, use a new key for a new operation. Examples:version_conflict,campaign_state_conflict,contact_already_exists,idempotency_key_reused. - Wait for Retry-After: Nothing was done. Wait
Retry-Afterseconds and retry with the same key. Examples:rate_limited,calling_capacity_reached,service_unavailable,campaign_operation_in_progress. - Schedule for later: Nothing was done, and the wait is hours, not seconds. Queue the work for after
details.resets_at(or start a smaller campaign that fits indetails.remaining). Example:daily_call_limit_reached. - Retry with backoff (reads) or check state first (writes): For a read, retry with exponential backoff. For a write, the
500is not stored, so a same-key retry runs it again: read the resource first and retry with the same key only if the change is not there. Example:internal_error.
The error-code reference lists every code with its status, retry hint and fix.
Handling errors in code
Read the envelope once, then decide from retry and, where it matters, from code. For example, this call uses one of your own agents that is not published yet:
JSON
retry is no, so sending the same request again would fail the same way. details.reasons says what to fix: publish the agent in the console, then send the request again. If you sent an Idempotency-Key, the refusal was not stored under it, so you may reuse it.
Decide the same way for every error:
codeisidempotency_outcome_unknownorcampaign_outcome_unknown: read the resource before anything else.codeisinternal_erroron a write: read the resource first and retry with the same key only if the change is not there. On a read, retry with backoff.retryisafter: wait the number of seconds inRetry-After, then send the same request with the same key.retryisyes: retry with exponential backoff and the same key.retryisno: fix the request or the configuration (detailssays what), then send it again.
Complete, runnable retry helpers are on Rate limits (backoff with Retry-After) and Idempotency (retries that can never place a call twice).
When your server gets no HTTP response at all (a timeout, a reset connection or a proxy error page that is not JSON), the request may or may not have run. With an Idempotency-Key, retry with the same key, then check state if the retries run out. Without one, check state first (see Idempotency): a blind retry can repeat the action.
Log what support needs
For every failed request, log at least: the time in UTC, the method and path, the HTTP status, error.code and error.request_id. Never log the Authorization header or the key. With the request_id, support can find exactly what happened.
All error codes
See the error-code reference: every code, its HTTP status, what it means, how to fix it and whether to retry. Each code also has its own page at /errors/<code>, which is the doc_url in the error.