Guides

YouTube Transcript API Errors: How to Handle 404, 429, and 503

Published 2026-11-14 · TextFrom

A failed transcript request does not always mean that a video has no captions. It might mean that a requested language is missing, the caller has exceeded an allowance, the video is unavailable, or an upstream service could not answer. Treating all of those cases as “no transcript” makes applications difficult to troubleshoot and can lead to unnecessary retries.

This guide uses the error codes of the TextFrom API to explain a practical approach. The goal is not to hide errors; it is to make each one actionable.

Start with the exact response

When a request fails, record a small set of useful facts: the HTTP status, the service's error.code, whether error.retryable is true, and the meta.request_id if supplied. For your own diagnosis, keep the requested language and the time of the request as well. Avoid copying API keys, full authentication headers, or caption contents into tickets or public issue reports.

The status tells you the general class of problem, but the specific error code provides a more useful instruction. For example, both a missing language and an unavailable video may use HTTP 404, yet the next action is different.

A missing-language response has this abbreviated illustrative structure (shortened from a real response; the request ID is elided):

{
  "error": {
    "category": "verdict",
    "code": "language_not_available",
    "retryable": false,
    "details": {"available": [{"lang": "de", "kind": "manual"}, {"lang": "en", "kind": "manual"}]}
  },
  "meta": {
    "request_id": "…",
    "service": "youtube.transcript",
    "operation": "transcript",
    "billable": false,
    "units": 0
  }
}

The available array lists the tracks the video does have; other failing responses carry different details. The request ID can help support locate an issue without asking the user to share a secret.

404: inspect the specific reason

The language_not_available code indicates that the requested language could not be delivered. If available languages are returned, show those choices or use them in a subsequent request only when the user has explicitly allowed the fallback. A request for Spanish should not silently become a successful English result merely because English happened to exist.

The other 404 codes are no_captions, video_unavailable, and video_restricted. These are not interchangeable. A video may be unavailable, limited, or genuinely lack a usable caption track. Your interface should use the service's specific code rather than asserting that the creator deliberately disabled captions in every case.

Do not repeatedly submit the same non-retryable request. First check the video ID, requested languages, track policy, and the caller's permissions. A developer report about a cloud-hosted transcript library shows why this matters: a request worked locally but reported disabled captions in a cloud environment. That report is evidence of a historical case, not proof that all 404s have the same cause or that a particular service behaves this way today.

429: identify the limit and wait

TextFrom distinguishes three HTTP 429 codes. rate_limit_exceeded is about speed: too many requests in a minute. quota_exceeded is about the monthly allowance, which resets on the 1st of each month at 00:00 UTC. translation_quota_exceeded is the separate monthly allowance for translated transcripts; plain transcripts keep working when only that one is used up. The console and the plan page show the current numbers, so read them there rather than copying values from a tutorial.

A useful client should slow down or wait until the relevant reset, then retry only when appropriate. If the response provides a Retry-After header, respect it. Do not let a batch job retry at full speed from every worker at the same time. A simple cap on attempts and some delay between attempts can prevent an avoidable pile-up.

The service's overall capacity is separate from an individual account allowance: capacity_exhausted is a 503 that means the shared daily total of the whole service is used up. When the service says it is at capacity, an account with unused allowance may still need to wait. Avoid telling users that they have personally exhausted their allowance unless the response actually says so.

503: temporary does not mean unlimited retries

upstream_error and upstream_refused are temporary, upstream-related HTTP 503 responses. The body includes a retryable field, and the response may include Retry-After. Treat a retry as a limited attempt, not a promise that the next call will work.

One reasonable application policy is to retry a small, fixed number of times, honor the server's indicated delay when available, and then return a clear message. Exact retry counts and delays depend on your latency and workload requirements. Record the final request ID and error code so that a support request can be investigated.

Do not turn an unknown or empty provider response into a definite “no captions” verdict in your own application. If the service cannot establish the reason, present the uncertainty rather than manufacturing a conclusion.

400 and 401: fix the request, not the retry loop

A 400 not_supported response means the request asks for something the service cannot do, for example speech-to-text for a video without captions, or translating from a language the translator does not know. A retry does not add support for a feature. A 400 translation_too_long means the video is too long to translate in one request: ask for the original language or use a shorter video. A 400 invalid_parameter names the parameter to fix.

A 401 invalid_token means the authentication needs attention. Confirm that your application reads the expected environment variable and sends the key in the Authorization: Bearer … header. If the key has been revoked or leaked, use the account's current key-management process. Never paste a live key into a public support post or a chatbot conversation.

A small JavaScript error-handling example

The following server-side example uses Node.js 18 or later with fetch available. Save it as an .mjs file so that top-level await works. Replace VIDEO_ID with an authorized video ID and supply your key via an environment variable. It logs the code and request ID; it does not implement automatic retries.

const apiKey = process.env.TEXTFROM_API_KEY;
if (!apiKey) throw new Error('Set TEXTFROM_API_KEY first');

const videoId = 'VIDEO_ID';
const url = new URL(
  `https://api.textfrom.dev/v1/youtube/videos/${encodeURIComponent(videoId)}/transcript`
);
url.search = new URLSearchParams({
  lang: 'en-US,en',
  kind: 'manual,auto',
  format: 'json',
});

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await response.json();

if (response.ok) {
  console.log('Delivered:', body.data.delivered);
} else {
  console.error({
    status: response.status,
    code: body.error?.code,
    retryable: body.error?.retryable,
    requestId: body.meta?.request_id,
    retryAfter: response.headers.get('Retry-After'),
  });
}

Keep the key in a server-side environment. Shipping a permanent API key in browser JavaScript exposes it to visitors. In a production client, also handle network failures and unexpected non-JSON responses; the short example focuses on the JSON results.

A practical checklist

Before retrying, ask: Is the input valid? Is the requested language actually one the application accepts? Is the code a temporary failure or a final result? Is the service asking you to wait? Can you report the request ID without exposing the key?

Then consult the error list in the TextFrom quickstart for the full set of codes. Official YouTube API errors have their own meanings and permissions; do not assume that a third-party service's 403, 404, or quota model is identical to the official API.

Sources: TextFrom quickstart; YouTube Data API errors; historical cloud-environment report. Accessed 2026-11-14 PT.

Try it with your own key. Free plan: sign up with an email and get a key at once.

Sign up free