Skip to main content
Every v2 error — validation, auth, billing, rate limiting — uses one envelope. There is no second shape to special-case.

Status codes

Error codes

error.code is an open enum. New codes are added without a version bump. Always have a default branch keyed on the HTTP status.
Foreign IDs are 404, never 403An agt_ or call_ ID that exists in another workspace is answered exactly as an ID that never existed: 404 not_found. Anything else would be an enumeration oracle — a way to probe which IDs are real on the platform by watching the status code change. 403 is reserved for one situation only: your own key has been revoked.

Handling errors

The pattern that covers everything: branch on status, then on code.
At the call site, only two codes deserve their own branch:

Retry rules

  • Retry 429, 500, 502, 503, 504 and network errors. Exponential backoff with jitter; honour Retry-After when present.
  • Never retry 400, 401, 403, 404, 501 — the same request will fail the same way.
  • 402 is retryable only after a top-up, not on a timer.
  • Always send Idempotency-Key on POST /v2/calls. A timeout tells you nothing about whether the phone rang; without the key, your retry is a second call to a real person.

What a 409 duplicate_call actually means

It means a request with this Idempotency-Key is still being processed. It is the narrow race, 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 say so. 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. Either way no second call is placed. That is usually the success path of a retry, not a failure: log it and move on, do not surface it as an error to your users.