Docs · Reference · Errors

Errors.

Every non-2xx response uses one envelope. The code is a stable machine-readable string — branch on it, not on the message text, which can change.

The envelope#

Error response body
{
  "error": {
    "code": "invalid_request",
    "message": "language_hint: must be a 3-letter ISO 639-3 code (e.g. \"ara\", \"eng\")"
  }
}

Validation messages name the offending field or file as field: problem, so one response tells you exactly what to fix.

Error codes#

FieldTypeDescription
invalid_request400The request failed validation: missing files field, a non-audio or oversized file, a malformed language_hint, a bad filter value or cursor… The message names the violation.
too_many_files400More than 25 files in one POST /v1/calls request. Split the batch.
invalid_api_key401Missing Authorization header, malformed key, unknown key, or revoked key — deliberately indistinguishable.
insufficient_credits402The org balance can't cover the whole upload batch. The message states exactly how many credits were needed and available. Nothing was stored or charged.
not_found404The call doesn't exist or belongs to another organization — the API doesn't distinguish.
rate_limit_exceeded429Per-key request budget exhausted for the current 60-second window. Comes with a retry-after header in seconds.
internal_error500Something broke on our side. Safe to retry with backoff; the failure is already logged and alerting.

Status mapping#

  • 2xx — success. 200 reads, lists, and full idempotent replays; 201 uploads that created at least one new call (analysis itself is asynchronous — poll or use webhooks).
  • 4xx — your request needs to change before retrying (except 429, which just needs time, and 402, which needs credits).
  • 5xx — our problem; retry with backoff.

402 insufficient_credits#

402 example
HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits: need 25, have 12. Top up or upgrade your plan."
  }
}

The balance is checked against the whole batch before anything is stored — no half-uploaded batch to clean up. Failed analyses refund automatically, so a 402 always reflects your true balance, not stuck holds.

429 rate_limit_exceeded#

429 example
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1783677160
retry-after: 21

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Back off and retry."
  }
}

Wait retry-after seconds (or until x-ratelimit-reset, a unix timestamp) and retry. Details on the window mechanics are in Rate limits.

Handling pattern#

Node 18+
const res = await fetch(API + "/calls", { method: "POST", headers, body: form });
if (!res.ok) {
  const { error } = await res.json();
  switch (error.code) {
    case "insufficient_credits": // 402 — top up, then retry the batch
    case "rate_limit_exceeded":  // 429 — wait retry-after seconds, retry
    case "too_many_files":       // 400 — split the batch (max 25 files)
    case "invalid_request":      // 400 — fix the payload, don't retry as-is
    case "invalid_api_key":      // 401 — check the key, check for revocation
    case "not_found":            // 404 — check the ID and org
    default:                     // internal_error etc. — retry with backoff
  }
  console.error(res.status, error.code, error.message);
}