YouTube transcripts that don't break on your server.

The pip library works on your laptop, then 429s from cloud IPs in production. This API handles the proxy rotation, retries and fallbacks so transcript fetching just works — from anywhere. One GET request, plain JSON, free to start.

Free — no key needed Works from cloud IPs YouTube blocks Live status
terminal
$ curl "https://api.youtubetotext.com/full_transcript/dQw4w9WgXcQ?meta=true"

{
  "transcript": [
    { "text": "♪ We're no strangers to love ♪",
      "start": 18.64, "duration": 3.24, "index": 1 },
    … more segments …
  ],
  "source_type": "human",
  "language": "English",
  "language_code": "en"
}

Quickstart

From video ID to transcript in 30 seconds

Take the 11 characters after v= in any YouTube URL — that's your video ID. Then make one request.

Request
curl "https://api.youtubetotext.com/full_transcript/dQw4w9WgXcQ?lang=en&meta=true"
Response — 200 OK
{
  "transcript": [
    {
      "text": "♪ We're no strangers to love ♪",
      "start": 18.64,
      "duration": 3.24,
      "index": 1
    },
    … one object per caption segment …
  ],
  "source_type": "human",
  "language": "English",
  "language_code": "en"
}
Base URL: https://api.youtubetotext.com — every endpoint below is relative to this. Served over HTTPS from Google Cloud Run.

API reference

Endpoints

Three endpoints. Every response — success or error — is JSON.

GET /full_transcript/{video_id} Fetch the full transcript of a video

Path parameters

ParameterTypeDescription
video_id string, required The YouTube video ID (the 11 characters after v=). Must match [A-Za-z0-9_-]{5,20} — anything else returns 400 before any lookup happens.

Query parameters

ParameterTypeDescription
lang string, optional Language code of the transcript to fetch, e.g. en. Use /list_transcripts to see which languages a video has.
meta boolean, optional When true, the response also includes source_type and language_code.

Response fields — 200 OK

FieldTypeDescription
transcriptarrayCaption segments, in order.
transcript[].textstringThe segment's text.
transcript[].startnumberStart time in seconds.
transcript[].durationnumberDuration in seconds.
transcript[].indexintegerPosition of the segment in the transcript.
source_typestringOnly with meta=true. "human" (creator-uploaded captions) or "auto" (YouTube auto-generated).
languagestringOnly with meta=true. Human-readable language name, e.g. "English".
language_codestringOnly with meta=true. Language code of the returned transcript, e.g. "en".
Check the body, not just the status code. A video that exists but has no captions returns 200 with an error object: {"error": …, "reason": …, "canTranscribeWithAI": true}. canTranscribeWithAI: true means the upcoming Pro AI-transcription layer will be able to handle that video.
GET /list_transcripts/{video_id} List a video's available caption tracks

Returns an array — one object per caption track the video has. Useful before requesting a specific lang.

Response fields — 200 OK (array items)

FieldTypeDescription
languagestringHuman-readable language name.
language_codestringCode to pass as ?lang=.
is_generatedbooleantrue if the track is YouTube auto-generated rather than creator-uploaded.
is_translatablebooleanWhether YouTube can translate this track to other languages.
translation_languagesarrayLanguage codes the track can be translated to (when translatable).
GET /health Service health check

Returns {"status": "ok"} when the API is up. This is the same endpoint the status page polls. Not rate-limited like the transcript endpoints, but please be reasonable.

Errors

Error handling

Every error is a JSON body — never an HTML page, never a bare 500. Unknown or unavailable videos return a structured JSON error too.

StatusWhen it happensWhat to do
200 + error field The video exists but has no captions. Body includes error, reason and canTranscribeWithAI: true. Check for the error key in every 200 response. Rarely this can also appear transiently for a captioned video when the upstream fetch fails — if you're sure the video has captions, retry once. AI transcription for truly caption-less videos is rolling out with Pro.
400 video_id fails validation — it must match [A-Za-z0-9_-]{5,20}. Send the bare video ID, not the full YouTube URL.
408 Processing took longer than 25 seconds. Retry the request — a later attempt often succeeds.
429 You exceeded a rate limit (see below). Back off and retry after a minute.

Rate limits

Free-tier limits

The free tier is keyless, so limits are per IP address. Exceeding either limit returns 429.

ScopeLimitOn exceed
Transcript endpoints10 requests / minute per IP429 — retry after a minute
Overall100 requests / hour429 — retry later

Hitting the limit from a cloud server? The $19 Starter pack gives reliable, higher-volume fetching.

Code examples

Call it from your language

Each example fetches a transcript, handles the no-captions case, and respects rate limits.

curl
# Fetch a transcript (with source + language metadata)
curl "https://api.youtubetotext.com/full_transcript/dQw4w9WgXcQ?lang=en&meta=true"

# List the caption tracks a video has
curl "https://api.youtubetotext.com/list_transcripts/dQw4w9WgXcQ"

# Health check
curl "https://api.youtubetotext.com/health"
Python 3 — requests
import requests

BASE = "https://api.youtubetotext.com"

resp = requests.get(
    f"{BASE}/full_transcript/dQw4w9WgXcQ",
    params={"lang": "en", "meta": "true"},
    timeout=30,
)
data = resp.json()  # errors are JSON too

if resp.status_code == 429:
    print("Rate limited — wait a minute, then retry")
elif "error" in data:
    print(f"No captions: {data['reason']} "
          f"(AI transcription possible: {data['canTranscribeWithAI']})")
else:
    text = " ".join(seg["text"] for seg in data["transcript"])
    print(f"[{data['source_type']}/{data['language_code']}] {text[:80]}…")
JavaScript — fetch
const BASE = "https://api.youtubetotext.com";

const res = await fetch(`${BASE}/full_transcript/dQw4w9WgXcQ?lang=en&meta=true`);
const data = await res.json(); // errors are JSON too

if (res.status === 429) {
  console.log("Rate limited — wait a minute, then retry");
} else if (data.error) {
  console.log(`No captions: ${data.reason}`);
  console.log(`AI transcription possible: ${data.canTranscribeWithAI}`);
} else {
  const text = data.transcript.map((seg) => seg.text).join(" ");
  console.log(`[${data.source_type}] ${text.slice(0, 80)}…`);
}

Pricing

Free to try. Pay for reliability.

Free captions cover most videos, keyless. The paid pack is for reliable, higher-volume fetching that keeps working from cloud servers.

Free

Live now
$0 forever
  • No API key, no signup
  • 10 requests / minute per IP on transcript endpoints
  • 100 requests / hour overall
  • Human + auto-generated captions
  • Every language YouTube has
  • Timestamped JSON segments
Start now — no signup

Starter pack

Available
$19 one-time · 10,000 fetches
  • Reliable fetching from blocked cloud IPs
  • Higher / unthrottled rate limits
  • Priority proxy rotation + retries
  • Direct email support from the developer
Buy a pack

Your access is set up by hand within a few hours of purchase (kept deliberately simple while this is early). AI transcription for caption-less videos is on the roadmap, not included yet.