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": {
"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#
| Field | Type | Description |
|---|---|---|
| invalid_request | 400 | The 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_files | 400 | More than 25 files in one POST /v1/calls request. Split the batch. |
| invalid_api_key | 401 | Missing Authorization header, malformed key, unknown key, or revoked key — deliberately indistinguishable. |
| insufficient_credits | 402 | The 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_found | 404 | The call doesn't exist or belongs to another organization — the API doesn't distinguish. |
| rate_limit_exceeded | 429 | Per-key request budget exhausted for the current 60-second window. Comes with a retry-after header in seconds. |
| internal_error | 500 | Something broke on our side. Safe to retry with backoff; the failure is already logged and alerting. |
Status mapping#
- 2xx — success.
200reads, lists, and full idempotent replays;201uploads 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, and402, which needs credits). - 5xx — our problem; retry with backoff.
402 insufficient_credits#
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#
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#
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);
}