Errors
Every failure, on every endpoint, in every case, is one envelope. code is what a program branches on — one of twelve values; message is a complete sentence written for a person and may be shown as-is; detail is always present, {} when there is nothing to add, and its keys are fixed per code; request_id equals the X-Request-Id response header and is the key the log line is under — quote it.
{
"error": {
"code": "quota_exceeded",
"message": "This account has used its monthly transcription allowance.",
"detail": {
"limit_seconds": 36000,
"used_seconds": 36012
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}Retry-After is sent on every 429 and every 503, always as delta seconds. Retry a 429 rate_limited, a 503 and — with backoff — a 500; never any other 4xx.
| code | HTTP | Retry? |
|---|---|---|
invalid_request | 400 | no |
unauthenticated | 401 | no |
forbidden | 403 | no |
not_found | 404 | no |
conflict | 409 | sometimes |
payload_too_large | 413 | no |
unsupported_media_type | 415 | no |
unprocessable_source | 422 | no |
rate_limited | 429 | after Retry-After |
quota_exceeded | 429 | yes, but not soon |
internal_error | 500 | yes, with backoff |
engine_unavailable | 503 | after Retry-After |
invalid_request — 400
What happened
A malformed body; an unknown field; a missing required field; a value outside an enumeration; a source object that is not exactly one of the three shapes; a bad part_index; a missing etag in multipart mode; a sha256 that is not 64 lowercase hex; a format outside output_format; a malformed or expired cursor; a limit outside [1, 100].
What to do
Fix the request. Every failing field is named in one response — never one at a time. Retryable: no.
Example
{
"error": {
"code": "invalid_request",
"message": "The field source.url is not a recognised YouTube URL shape.",
"detail": {
"field": "source.url",
"reason": "is not a recognised YouTube URL shape"
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}unauthenticated — 401
What happened
No Authorization header and no session cookie; a token that does not match a live key; a revoked key; an expired session.
What to do
Send a live key as Authorization: Bearer sk_live_…. detail is deliberately empty: distinguishing "no such key" from "revoked" would be an oracle. Retryable: no.
Example
{
"error": {
"code": "unauthenticated",
"message": "This request carried no valid credential.",
"detail": {},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}forbidden — 403
What happened
The key lacks the route's scope; a cookie-authenticated state-changing request from a foreign Origin; a destination url that is not https:// or resolves into a private network.
What to do
Create a key with the scope detail.required_scope names, or fix the destination URL. Never 404: the resource exists and you are authenticated. Retryable: no.
Example
{
"error": {
"code": "forbidden",
"message": "This key does not have the runs:write scope.",
"detail": {
"required_scope": "runs:write",
"scopes": [
"runs:read"
]
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}not_found — 404
What happened
An id that does not exist, or exists in another account. The two are indistinguishable by design.
What to do
Check the id. Do not retry. Retryable: no.
Example
{
"error": {
"code": "not_found",
"message": "There is no run with that id in this account.",
"detail": {
"resource": "run",
"id": "0195c8e4-8d02-7c19-b3e7-5a1f9d20c68b"
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}conflict — 409
What happened
Cancelling a run that is already terminal; a part reported against an upload that is not open; completing an upload whose parts do not reconcile; aborting a complete upload; requesting a transcript for a run that is not done; delivering a run that is not terminal or to a disabled destination.
What to do
Read detail.status: it is the resource's actual state, and that decides whether waiting helps. A transcript on a running run: poll and try again. A cancel on a done run: nothing to do. Retryable: sometimes.
Example
{
"error": {
"code": "conflict",
"message": "The transcript is not ready: the run is still running.",
"detail": {
"status": "running"
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}payload_too_large — 413
What happened
declared_bytes above the account byte ceiling; received_bytes crossing it at a part report; a request body above 1 MiB.
What to do
The ceiling is in detail.limit_bytes. Reduce the media or ask the operator for a higher ceiling. Retryable: no.
Example
{
"error": {
"code": "payload_too_large",
"message": "This upload declares 3.2 GB and the ceiling for this account is 2.1 GB.",
"detail": {
"limit_bytes": 2147483648,
"declared_bytes": 3221225472
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}unsupported_media_type — 415
What happened
A request body without Content-Type: application/json; an Accept header that admits nothing this endpoint can produce.
What to do
Send Content-Type: application/json. Retryable: no.
Example
{
"error": {
"code": "unsupported_media_type",
"message": "Request bodies must be sent as application/json.",
"detail": {
"expected": "application/json"
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}unprocessable_source — 422
What happened
A syntactically valid request whose source cannot be worked with: an unrecognised YouTube URL; an advisory duration above the ceiling; an upload_id that belongs to another account or is already consumed by a run.
What to do
Read detail.reason. Fix the source; a retry with the same source will fail the same way. Retryable: no.
Example
{
"error": {
"code": "unprocessable_source",
"message": "That is not a YouTube URL this service recognises.",
"detail": {
"reason": "unrecognised_youtube_url"
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}rate_limited — 429
What happened
The request rate ceiling for this key or account was exceeded. Nothing was consumed.
What to do
Wait Retry-After seconds and send the same request. detail.scope says whether the key or the account ceiling answered. Pace on the RateLimit-* headers so you never get here. Retryable: after Retry-After.
Example
{
"error": {
"code": "rate_limited",
"message": "This key has sent more than 60 requests in the last minute.",
"detail": {
"scope": "key",
"limit": 60,
"window_seconds": 60
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}quota_exceeded — 429
What happened
The account's monthly seconds allowance is spent; the concurrent-open-upload ceiling is reached; the rolling daily byte ceiling is reached; more than 20 destinations.
What to do
Retry-After is honest and can be weeks. An allowance is spent; time or an operator changes it. Do not loop on it. Retryable: yes, but not soon.
Example
{
"error": {
"code": "quota_exceeded",
"message": "This account has used its monthly transcription allowance.",
"detail": {
"limit_seconds": 36000,
"used_seconds": 36012
},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}internal_error — 500
What happened
An unhandled exception. The message never carries it.
What to do
Retry with exponential backoff. Quote request_id if it persists — it is the key the log line is under. Retryable: yes, with backoff.
Example
{
"error": {
"code": "internal_error",
"message": "Something went wrong on our side. Quote the request id if it happens again.",
"detail": {},
"request_id": "req_0195c8e4-9a10-7d3e-8f21-0b6c4d2e7a55"
}
}A failed run is not an HTTP error
An API error is about your request; error_class on a run is about the work. POST /v1/runs answered 201: the request was right and the work was accepted. If the source turns out to be members-only, or scheduled, or has no audio track, the run reaches status: "failed" with an error_class naming why, and every response about that run is a normal 200. A client that treats a failed run as an HTTP failure will retry forever against a source that will never work; a client that treats a 4xx as a failed run will wait for a webhook that will never come.
| API error | Failed run | |
|---|---|---|
| Where | the response to this request | the run resource, later |
| Shape | { "error": { "code": … } } with a 4xx/5xx | { "status": "failed", "error_class": … } with a 200 |
| Branch on | error.code — twelve values | error_class — nineteen values, the reference lists them |
| Retry? | only 429, 500, 503, per Retry-After | only premiere, as a new run after retry_after |