Skip to main content
VARTA

What do VARTA API errors look like and how do I handle them?

Every error from /v1 — regardless of what went wrong inside the platform — comes back as the same JSON shape, with an HTTP status that matches it, with one exception: unauthenticated can also arrive as a bare {"detail": "authentication required"} with no envelope at all, rejected before a /v1 handler ever runs — see that page for when.

{
  "error": {
    "type": "invalid_request_error",
    "code": "workflow_not_found",
    "message": "No agent with id 'ag_x' exists for this tenant.",
    "param": "agent_id",
    "request_id": "req_a91c4e7b2f30",
    "doc_url": "https://vartavoice.ai/docs/errors/workflow_not_found"
  }
}
  • type is the coarse bucket a client branches on — invalid_request_error, authentication_error, permission_error, billing_error, rate_limit_error, provider_error, or api_error.
  • code is the stable contract. message is for humans and may be reworded — write your error handling against code, never against the text of message.
  • param names the offending request field, when the error is about one specific field. It's null otherwise.
  • request_id is unique per request. Quote it when you contact support about a 5xx.
  • doc_url points at https://vartavoice.ai/docs/errors/<code> — the value in the JSON example above is what the platform emits (DOC_BASE in backend/app/api/v1/errors.py), and it is the page for that code on this site: the same one the table's links below open. It is safe to follow, and safe to use as a stable identifier for the code.

Unrecognised errors still parse

Only the codes in the table below are permanent. If the platform hits an internal failure it hasn't given a specific code, it degrades to one of a small set of per-status fallback codes (for example, an unrecognised 404 becomes not_found, an unrecognised 429 becomes rate_limit_exceeded) rather than breaking the envelope shape. Two statuses have no fallback entry at all and so land on internal_error while keeping their own status: a 405 (wrong HTTP method for a /v1 path) and a 415 (unsupported media type) both arrive as code: "internal_error" with status 405 / 415 respectively (backend/app/api/v1/errors.py:99-111) — so an internal_error on a non-5xx status is a malformed request, not a platform fault, and retrying it unchanged will not help. So it's safe to build a client that switches on type and a known subset of codes, and falls through to message for anything else — you will never get back something that isn't this envelope.

All error codes

Code HTTP status Type
invalid_request 400 invalid_request_error
validation_error 422 invalid_request_error
unauthenticated 401 authentication_error
invalid_api_key 401 authentication_error
token_expired 401 authentication_error
insufficient_credits 402 billing_error
insufficient_role 403 permission_error
insufficient_scope 403 permission_error
tenant_forbidden 403 permission_error
destination_not_permitted 403 permission_error
not_found 404 invalid_request_error
workflow_not_found 404 invalid_request_error
session_not_found 404 invalid_request_error
campaign_not_found 404 invalid_request_error
step_not_found 404 invalid_request_error
trunk_not_found 404 invalid_request_error
session_already_ended 409 invalid_request_error
idempotency_key_reuse 409 invalid_request_error
idempotency_key_in_progress 409 invalid_request_error
caller_id_not_on_trunk 400 invalid_request_error
dial_failed 502 provider_error
telephony_not_configured 503 provider_error
rate_limit_exceeded 429 rate_limit_error
concurrency_limit_exceeded 429 rate_limit_error
internal_error 500 api_error
provider_error 502 provider_error
provider_unavailable 503 provider_error
provider_timeout 504 provider_error

Each page answers three questions: what the code means, what causes it in this platform, and what to do about it. Where the underlying code has no live path that raises it yet, the page says so directly rather than guessing.