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.
$ 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.
curl "https://api.youtubetotext.com/full_transcript/dQw4w9WgXcQ?lang=en&meta=true"
{
"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"
}
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.
Path parameters
| Parameter | Type | Description |
|---|---|---|
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
| Parameter | Type | Description |
|---|---|---|
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
| Field | Type | Description |
|---|---|---|
transcript | array | Caption segments, in order. |
transcript[].text | string | The segment's text. |
transcript[].start | number | Start time in seconds. |
transcript[].duration | number | Duration in seconds. |
transcript[].index | integer | Position of the segment in the transcript. |
source_type | string | Only with meta=true. "human" (creator-uploaded captions) or "auto" (YouTube auto-generated). |
language | string | Only with meta=true. Human-readable language name, e.g. "English". |
language_code | string | Only with meta=true. Language code of the returned transcript, e.g. "en". |
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.
Returns an array — one object per caption track the video has. Useful before requesting a specific lang.
Response fields — 200 OK (array items)
| Field | Type | Description |
|---|---|---|
language | string | Human-readable language name. |
language_code | string | Code to pass as ?lang=. |
is_generated | boolean | true if the track is YouTube auto-generated rather than creator-uploaded. |
is_translatable | boolean | Whether YouTube can translate this track to other languages. |
translation_languages | array | Language codes the track can be translated to (when translatable). |
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.
| Status | When it happens | What 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.
| Scope | Limit | On exceed |
|---|---|---|
| Transcript endpoints | 10 requests / minute per IP | 429 — retry after a minute |
| Overall | 100 requests / hour | 429 — 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.
# 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"
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]}…")
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- 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
Starter pack
Available- Reliable fetching from blocked cloud IPs
- Higher / unthrottled rate limits
- Priority proxy rotation + retries
- Direct email support from the developer
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.