Errors
Every error response (4xx and 5xx) uses a stable envelope. Branch on error.type, not on the HTTP status code alone. Keep request_id in your logs so failed requests can be traced.
Error envelope
error.type is a stable enum value (see the taxonomy below). error.message is human-readable and may include specifics like remaining-vs-needed counts. error.details is optional, present only on types that carry structured detail. request_id is a 24-hex-character identifier that also appears in the X-Request-Id response header.
{
"error": {
"type": "rate_limited",
"message": "Rate limit exceeded",
"details": {}
},
"request_id": "req_a1b2c3d4e5f6a7b8c9d0e1f2"
}The same request_id is returned in the X-Request-Id response header on every response, success or failure. If you send X-Request-Id with a value up to 128 characters, the server uses it instead. This is useful when matching API responses to your own logs.
Error type taxonomy
The values of error.type are part of the wire contract: never renamed, never removed, only added.
| Type | HTTP | Meaning |
|---|---|---|
bad_request | 400 | Validation failed: malformed video ID, missing required body field, bad Idempotency-Key, etc. |
unauthorized | 401 | Missing or invalid API key. See Authentication. |
insufficient_quota | 402 | Not enough credits to fulfill the request. See Quotas. |
forbidden | 403 | Authenticated but the plan doesn't allow this action (e.g. free plan submitting a bulk job, free plan requesting a non-txt / non-json format). |
not_found | 404 | Resource doesn't exist or doesn't belong to your account. |
conflict | 409 | Generic conflict, for example canceling an already-canceled job, exporting a job that's still processing. |
idempotency_conflict | 409 | Idempotency-Key reused with a different body, or duplicate request in flight. See Idempotency. |
gone | 410 | Resource existed but is no longer available. Today this fires when a bulk-job export has expired (30 days after terminal). |
request_too_large | 413 | Bulk job exceeds your plan's per-job video cap. |
rate_limited | 429 | Per-minute rate limit exceeded, or active-bulk-job cap reached. See Rate limits. |
service_degraded | 503 | Bulk job submissions are temporarily paused because transcript fetching is degraded. Sync transcript requests still run. |
internal_error | 5xx | Server-side error or upstream failure. Customers can see HTTP 500, 502, or 504 all carrying this type. Retry with backoff. |
Recommended client behavior
Branch on error.type:
const res = await fetch(url, { headers });
if (!res.ok) {
const body = await res.json();
const { type, message } = body.error;
const requestId = body.request_id;
switch (type) {
case "rate_limited":
// sleep for Retry-After seconds, then retry
break;
case "insufficient_quota":
// surface to the user, link to /pricing
break;
case "service_degraded":
// backoff, try again in a few minutes
break;
case "unauthorized":
case "forbidden":
// not retryable: fix the key or plan
break;
case "internal_error":
// log requestId, retry with exponential backoff
break;
default:
// 4xx: fix the request before retrying
break;
}
}Type stability
Error types are only added, never renamed. Treat unknown types by falling back to the HTTP status code.