Concepts

Errors

Voximate answers with standard HTTP status codes, and every error body has the same shape — so one error handler covers the whole API.

The error shape

Response · 400
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "`to` must be a phone number in E.164 format, for example +4712345678.",
    "param": "to"
  },
  "request_id": "req_8f14e45fceea167a"
}
  • type — the broad category. Branch on this first.
  • code — a stable, specific reason. Safe to branch on; codes are never renamed within v1.
  • message — a sentence for people. Log it, show it to a developer, but do not parse it: wording may improve.
  • param — the field the error is about, or null.
  • request_id — the same value as the X-Request-Id header. Log it, and quote it to support.

Error types

typeHTTP statusMeaning
invalid_request_error400, 422The request is malformed (400), or valid but impossible right now (422).
authentication_error401No key, or the key is invalid, expired or revoked.
permission_error403The key lacks the scope or role this needs.
not_found_error404No such resource in your workspace.
idempotency_error409An Idempotency-Key clash.
rate_limit_error429Too many requests, too many calls, or the plan quota is used up.
api_error5xxSomething went wrong on our side. Safe to retry with backoff.

Error codes

The codes you are most likely to meet. New codes may be added; treat an unknown one by its type.

codeStatusWhat to do
parameter_missing400A required field or parameter is missing. param names it.
parameter_invalid400A field has the wrong type or format. param names it.
parameter_unknown400A field this endpoint does not take. Unknown fields are refused, never ignored. param names it.
body_invalid400The body is not valid JSON, or not a JSON object.
body_empty400A PATCH with nothing to change. Send at least one field.
body_too_large400The request body is too large.
content_type_invalid400Send bodies as Content-Type: application/json.
request_invalid400The request could not be read.
api_key_missing401No Authorization: Bearer … header.
api_key_invalid401The key is wrong, expired or revoked.
missing_scope403The key needs a scope it does not have. The message names it.
role_required403Adding, changing, testing or removing a webhook endpoint needs a key made by a workspace owner or admin.
resource_missing404Nothing with that id in your workspace.
route_not_found404No such endpoint. Check the method and the path.
idempotency_key_reused409This Idempotency-Key was used with a different request.
idempotency_request_in_progress409The first request with this key is still running. Retry shortly.
agent_not_published422The agent must be published before it can place calls.
agent_not_ready422The agent cannot be published or unpublished as it is. The message says why.
agent_has_no_number422The agent has no phone number to call from. Assign one first.
from_number_required422The agent has several numbers. Say which one with from.
from_number_not_assigned422from is not one of the numbers assigned to this agent.
emergency_number422Emergency numbers can never be called by an agent.
call_rejected422The call could not be started (for example the free trial allows one call at a time). The message says why.
rate_limited429Too many requests. Wait Retry-After seconds.
call_rate_limited429Too many outbound calls in the last minute.
call_daily_limit429The daily outbound call limit is reached.
quota_exceeded429Your plan’s call quota is used up.
internal_error500Something went wrong on our side. Retry; if it keeps happening, contact us with the request id.
upstream_error502A Voximate service answered with an error. Retry with backoff.
upstream_unavailable503A Voximate service did not answer in time. Retry with backoff.
auth_unavailable503The key could not be checked just now. Retry with backoff.
call_outcome_unknown503POST /calls only: we could not confirm whether the call started, and it may be ringing. Look for it with GET /calls before trying again with a new Idempotency-Key.

Handling errors

  • Retry only 429 and 5xx. Every other 4xx will fail the same way again until the request changes.
  • On 429, wait the Retry-After seconds. On 5xx, back off exponentially with jitter.
  • Retry a POST only with an Idempotency-Key, so a retry can never start a second call or create a second agent.
  • Log request_id with every failure.
voximate.js
// Retry 429 and 5xx with exponential backoff; never retry other 4xx.
async function voximate(path, init = {}, attempt = 0) {
  const res = await fetch(`https://api.voximate.no/public/v1${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.VOXIMATE_API_KEY}`,
      'Content-Type': 'application/json',
      ...init.headers,
    },
  });
  const retryable = res.status === 429 || res.status >= 500;
  if (retryable && attempt < 4) {
    const retryAfter = Number(res.headers.get('Retry-After'));
    const wait = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : Math.min(30_000, 500 * 2 ** attempt) + Math.random() * 250;
    await new Promise((r) => setTimeout(r, wait));
    return voximate(path, init, attempt + 1);
  }
  const body = await res.json();
  if (!res.ok) {
    const err = new Error(body.error.message);
    Object.assign(err, { type: body.error.type, code: body.error.code, requestId: body.request_id });
    throw err;
  }
  return body;
}