# Errors

> The error envelope, every error code, and which ones to retry.

Canonical: https://transcript1.com/docs/errors
Last updated: 2026-11-05

A request error means the API refused the call. A job failure means the API accepted the transcription, but it later ended with `status` set to `failed`.

## The error envelope

Every `/v1` request error has the same shape:

```json
{
  "error": {
    "code": "insufficient_minutes",
    "message": "Not enough minutes",
    "retryable": false,
    "action_url": "https://transcript1.com/dashboard/billing",
    "minutes_needed": 20,
    "minutes_remaining": 3
  }
}
```

Handle errors by `code`. Display `message` to the user; its wording may change. When `retryable` is `true`, the same request may succeed later.

Some errors add fields next to `retryable`:

| Field | On which errors | Meaning |
|---|---|---|
| `action_url` | `insufficient_minutes`, `no_active_plan`, `topup_needs_user` | Where to buy a plan or a top-up. |
| `minutes_needed`, `minutes_remaining` | `insufficient_minutes` | Minutes the request needs (`null` when it is not known yet) and minutes you have. |
| `reason`, `packs` | `topup_needs_user` | Why the card was not charged, and the packs that were requested. |
| `supported_platforms` | `unsupported_platform` | The platforms you can send: `["youtube", "x", "tiktok", "instagram", "facebook", "bluesky", "twitch", "dailymotion", "pinterest", "linkedin", "snapchat"]`. |
| `missing_chunks` | `upload_incomplete` | Indexes of the chunks that did not arrive. |
| `max_bytes` | `media_too_large` | The largest file size allowed for you, in bytes. |
| `params` | `invalid_request` | For body and query validation errors, an object mapping each field to what is wrong with it. |

## Request errors

| HTTP status | `code` | Retryable | When |
|---|---|---|---|
| 400, 422 | `invalid_request` | No | A parameter or body field is missing or invalid, for example `diarize` together with `source: "captions"`, or `reuse_recent` with `webhook_url`. Also returned when the request body is not valid JSON. |
| 422 | `invalid_url` | No | Use a YouTube, X, TikTok, Instagram, Facebook or Bluesky link, or another platform listed in [Sources and options](https://transcript1.com/docs/sources-and-options). Also returned when `platform` does not match the link. |
| 422 | `unsupported_platform` | No | `platform` is not supported. |
| 422 | `unsupported_media` | No | The uploaded file does not have a supported extension. |
| 401 | `unauthorized` | No | The credential is missing, invalid, expired or revoked, or the upload token is invalid or expired. |
| 403 | `insufficient_scope` | No | An OAuth token lacks `transcriptions:read` or `transcriptions:write`. |
| 403 | `email_not_verified` | No | Verify your email address before creating transcriptions or live sessions. |
| 402 | `insufficient_minutes` | No | Your usable minutes are 0 or fewer than the transcription needs, or fewer than one started minute of a live session. When [auto top-up](https://transcript1.com/docs/billing-and-limits#auto-top-up) is on, transcript1 tries to top up first. Buy minutes at `action_url`, or let an agent buy them with `POST /v1/topups` or the MCP tool `top_up`. |
| 402 | `no_active_plan` | No | You tried to buy a top-up without an active paid plan. `POST /v1/topups` and the dashboard's billing endpoints return this, not the transcription endpoints. |
| 402 | `topup_needs_user` | Only for `pending` and `in_progress` | `POST /v1/topups` did not charge the card. `reason` says why: `not_allowed` (the credential cannot buy top-ups, or its user is not a team admin), `cap_reached` (the monthly agent limit has no room), `card_declined`, `authentication_required` or `no_card`. The user can buy the packs at `action_url`. For `pending` (the payment is not confirmed yet) and `in_progress` (another top-up is running), the charge may still complete: retry later with the same `Idempotency-Key` instead of sending the user to buy. |
| 404 | `not_found` | No | The transcription does not exist or belongs to another team. |
| 409 | `upload_incomplete` | No | Some chunks are missing. Send them and complete again. |
| 409 | `invalid_state` | No | The transcription is not awaiting an upload, so chunks can no longer be sent or completed. |
| 409 | `session_ended` | No | A live session has ended, so `POST /v1/live/sessions/:id/token` cannot issue a token. Also sent as a WebSocket `error` event. |
| 422 | `unsupported_language` | No | The `language` of a live session is not supported. |
| 422 | `unsupported_translation` | No | The `translate_to` language of a live session is not supported, or live translation is not available. |
| 409 | `idempotency_conflict` | No | The `Idempotency-Key` was already used with different parameters. |
| 413 | `media_too_large` | No | The file is larger than your limit. See [Uploads](https://transcript1.com/docs/uploads). |
| 413 | `invalid_request` | No | A JSON body over 1 MB. Files use `media_too_large` instead. |
| 429 | `rate_limited` | Yes | Too many requests per minute. Wait for `Retry-After` seconds. |
| 429 | `too_many_active_jobs` | Yes | Too many transcriptions running, or too many open uploads. Wait for `Retry-After` seconds. |
| 429 | `too_many_live_sessions` | Yes | Your plan's simultaneous live sessions are in use. Wait for `Retry-After` seconds (30). |
| 500 | `internal_error` | Yes | Something went wrong on our side. Retry shortly. |
| 503 | `service_unavailable` | Yes | transcript1 is temporarily unavailable. Retry shortly. |
| 503 | `live_capacity` | Yes | All live capacity is in use. Wait for `Retry-After` seconds (30) and create the session again. |
| 503 | `live_unavailable` | No | Live transcription is not available right now. |

Requests from a browser that use a login cookie instead of a bearer credential are also checked for their origin. A request from another site returns `403 forbidden_origin`. API calls with an `Authorization` header are not affected.

Every `429` response includes a `Retry-After` header with the number of seconds to wait. See [Billing and limits](https://transcript1.com/docs/billing-and-limits) for the limits themselves.

## Retry a request

- Retry only when `retryable` is `true`: `rate_limited`, `too_many_active_jobs`, `too_many_live_sessions`, `live_capacity`, `internal_error`, `service_unavailable`, and `topup_needs_user` with `reason` `pending` or `in_progress`. Honor `Retry-After` when it is present and back off between attempts otherwise.
- When retrying `POST /v1/transcriptions` or `POST /v1/topups`, send the same `Idempotency-Key` header to avoid a second charge if the original request succeeded.
- Fix the request before sending it again for every other code.

## Live transcription

`POST /v1/live/sessions` and the other live endpoints use the same envelope. `invalid_request`, `unsupported_language`, `insufficient_minutes` and `email_not_verified` apply to them as well, together with the live codes in the table above. Creating a session is not idempotent, so a retry after `too_many_live_sessions` or `live_capacity` creates a new session.

After the session exists, problems arrive on the WebSocket as `error` events and close codes, not as HTTP errors. See [Live transcription](https://transcript1.com/docs/live#websocket-protocol) for every event.

| Close code | Meaning |
|---|---|
| `1000` | Normal close, after `session.ended`. |
| `1011` | Unexpected server error. Resume with a new token. |
| `1012` | Server restart, after `reconnect`. Resume with a new token. |
| `4001` | Invalid, expired or already used token. |
| `4004` | Session not found or already ended. |
| `4008` | Protocol violation: an odd-length or oversized audio frame. |
| `4010` | Replaced by a newer connection to the same session. |

A live session that ends for a reason such as `balance_exhausted` or `idle_timeout` is not an error: it sends `session.ended` with an end reason and is saved. See [End reasons](https://transcript1.com/docs/live#end-reasons).

## Job failures

When a transcription ends with `status` set to `failed`, the object has `error: { "code", "message" }`. Failures can happen after the request returned, so read `error` when you poll or in the `transcription.failed` [webhook](https://transcript1.com/docs/webhooks). A failed translation sends a `transcription.translation.failed` webhook and never fails the transcript.

| `error.code` | Meaning | Try again? |
|---|---|---|
| `media_unavailable` | The video does not exist or is no longer available. | No. Check the URL. |
| `media_private` | The video is private. | No. Use a public post or a public or unlisted YouTube video. |
| `login_required` | The platform requires a logged-in account. | No. Permanent failure; not retried. |
| `no_audio` | The video has no sound track. | No. Permanent failure; not retried. |
| `age_restricted` | The video is age-restricted. | No. Upload the file instead. |
| `live_stream` | The video is a live stream, or a stream that has not started yet. | After the stream has ended. |
| `no_captions` | `source` was `captions` and the video has no usable caption track. | Use `source: "auto"` or `"stt"`. |
| `media_too_long` | Speech-to-text accepts up to 5 hours of media. | No. Split the media. |
| `unsupported_media` | The file could not be read or has no audio track. | No. Send a supported file. |
| `no_speech` | No speech was detected. | No. |
| `unsupported_language` | The language of this media is not supported. | No. Set `language` to a supported code or `"auto"`. |
| `insufficient_minutes` | Your minutes ran out before the transcription could be charged, and auto top-up, if on, could not add more. | After you top up. |
| `fetch_failed` | Fetching the media failed after several attempts. | Yes, later. |
| `transcription_failed` | Speech-to-text failed after several attempts. | Yes, later. |

Failed and canceled transcriptions are never charged. Minutes that were taken for a transcription are returned when it fails or is canceled.
