Skip to main content
Most v2 errors use the envelope below. Two validation errors are exceptions: tool definitions return {"error":"invalid_tools","details":[...]} and node graphs return {"error":"invalid_flow","details":[...]}, each with every offending field path. Flow generation uses the same shape for flow_generation_failed. Handle those shapes when saving agent tools or a flow, or generating one.

Status codes

Error codes

error.code is an open enum. We add new codes without a version bump, so always keep a default branch keyed on the HTTP status.
Foreign IDs are 404, never 403An agt_, call_, cmp_ or trn_ ID that exists in another workspace is answered exactly as an ID that never existed: 404 not_found. Any other answer would be an enumeration oracle, letting someone probe which IDs are real on the platform by watching the status code change. 403 never means “someone else’s ID”. It means your own key has been revoked, or your workspace is not allowed what the request asks for (contract_unavailable, recording_disabled).

Handling errors

To handle every error, branch on the status first and then on the code.
At the call site, only two codes need their own branch:

Retry rules

  • Retry 429, 500, 502, 503, 504 and network errors. Use exponential backoff with jitter, and honour Retry-After when present.
  • Never retry 400, 401, 403, 404, 501. The same request will fail the same way. 503 campaigns_unavailable and 503 transcription_unavailable belong in this group too, because they clear only when the feature is provisioned.
  • 402 is retryable only after a top-up, not on a timer.
  • Always send Idempotency-Key on POST /v2/calls. A timeout does not tell you whether the phone rang, and without the key, your retry places a second call to a real person.

What a 409 duplicate_call actually means

A request with this Idempotency-Key is still being processed. This only happens in a narrow race and is not the normal retry path. We cannot replay a response that has not been produced yet, and we must not place a second call, so we return this error instead. When the original request completes, the same key replays its stored response, so a retry a moment later returns the original 202 and the original call_id. In both cases no second call is placed. For a retry this is usually a success, so log it and move on instead of showing it to your users as an error.