YouTube Transcript API: The Complete Guide (2026)
Two ways to pull YouTube transcripts in code: the open-source Python library and a hosted REST API. Real code, real limits, and how to choose between them.
TL;DR: There are two ways to pull YouTube transcripts in code. The free youtube-transcript-api Python library works great until you hit IP blocks at scale. A hosted REST API (like TubeAlfred’s /v1/youtube/video/{id}/transcript) solves that with a $5/month plan that includes 3,500 shared credits and a single Authorization: Bearer header. This guide shows real code for both, where each one breaks, and how to choose.
If you’re a developer searching for “youtube transcript api,” you’re in one of two camps.
You’re prototyping and want to know which library to install. Or you’ve been running a transcript pipeline for a while, YouTube has started blocking your IP, and you’re shopping for a managed alternative.
This guide covers both paths with working code and the failure modes nobody mentions in the README.
What “YouTube Transcript API” Actually Means
There is no official YouTube Transcript API from Google. The YouTube Data API v3 covers videos, channels, comments, and playlists, but transcripts and captions sit outside that surface for most developers, OAuth gymnastics aside.
So when developers say “YouTube transcript API,” they mean one of two things:
- The open-source Python library at
github.com/jdepoix/youtube-transcript-api, installed asyoutube_transcript_api. Free, self-hosted, the default first stop. - A hosted REST API that wraps transcript retrieval, handles IP rotation, and exposes a simple HTTP endpoint. Paid, managed, what you graduate to when the library stops working in production.
The first option is faster to start. The second is faster to scale. Knowing when to switch is the whole game.
Option 1: The youtube-transcript-api Python Library
This is the default. It’s free, it’s MIT licensed, and it works for the first hundred or thousand requests of any project.
Install and Pull a Transcript in 3 Lines
pip install youtube-transcript-api
from youtube_transcript_api import YouTubeTranscriptApi
transcript = YouTubeTranscriptApi.get_transcript("dQw4w9WgXcQ")
print(transcript)
You get back a list of dicts with text, start, and duration for each segment:
[
{"text": "We're no strangers to love", "start": 18.8, "duration": 4.7},
{"text": "You know the rules and so do I", "start": 23.5, "duration": 3.2},
...
]
Selecting a Language
If a video has multiple caption tracks, pass languages as an ordered preference list. The library returns the first match.
transcript = YouTubeTranscriptApi.get_transcript(
"VIDEO_ID",
languages=["es", "en"]
)
Listing Available Tracks Before Fetching
Useful if you want to know what’s available without pulling everything:
transcript_list = YouTubeTranscriptApi.list_transcripts("VIDEO_ID")
for t in transcript_list:
print(t.language, t.language_code, t.is_generated, t.is_translatable)
The is_generated flag tells you if it’s an auto-caption (machine-generated) or a manually uploaded track. This matters more than most tutorials admit. Auto-captions on long videos are noisy, missing punctuation, and prone to mishearing brand names. If you’re feeding transcripts into an LLM for summarization, prefer manual tracks when they exist.
Translating Captions
The library can request a translated version of an existing track:
transcript_list = YouTubeTranscriptApi.list_transcripts("VIDEO_ID")
english = transcript_list.find_transcript(["en"]).translate("es").fetch()
This calls YouTube’s own translation, not a separate service. Quality is mediocre but free.
Where the Library Breaks in Production
For prototypes, the library is fine. For a real product, here’s what bites you, in the order it usually happens.
1. IP Blocks from YouTube
The library makes direct, unauthenticated HTTP requests to YouTube’s caption endpoints. After enough requests from one IP, YouTube returns a 429 or a captcha, and you get nothing back.
The threshold isn’t published. Reports range from 50 to a few thousand requests before a block kicks in, depending on the IP’s reputation. On a fresh AWS or DigitalOcean address, you might get blocked within a single batch job.
Workarounds:
- Rotate residential proxies. Expensive, fragile, against most provider TOS.
- Add
cookies.txtwith a real YouTube session. Helps marginally, eventually invalidates. - Run the library from a serverless function with a rotating egress. Works until the cloud provider’s IP range gets blocklisted.
None of these scale well. This is the single biggest reason teams migrate off the library.
2. Silent Schema Drift
YouTube changes the underlying caption format occasionally. The maintainer is responsive, but if you pin a version and YouTube ships a change, your pipeline returns empty lists or raises TranscriptsDisabled for videos that clearly have captions. Self-hosting means you own the upgrade cadence.
3. No Batch Endpoint
You’re looping over video IDs one at a time. For a channel with 500 videos, that’s 500 sequential HTTP calls, each ~500ms minimum. A real batch pipeline takes minutes per channel, not seconds.
You can parallelize the loop, but that accelerates problem #1: more concurrent requests from one IP means you hit YouTube’s block threshold faster. This is exactly the constraint hosted APIs remove. In our testing, dedicated transcript services like TranscriptAPI sustained 12+ concurrent requests without throttling, returning cached transcripts in ~200ms and fresh ones in ~3s — throughput the library can’t reach from a single IP at any level of concurrency. TubeAlfred’s endpoint handles the same parallel pattern, with rate-limit headers (X-RateLimit-Remaining) so your loop can pace itself instead of discovering the ceiling by hitting it.
Either way, “batch” with a hosted API still means a concurrent client-side loop — but one where the ceiling is a documented rate limit, not an unpublished IP-reputation block.
4. No Rate Limit Headers
The library returns a transcript or an exception. There’s no equivalent of X-RateLimit-Remaining to let you back off gracefully. You discover the block by getting blocked.
5. No Formal Reliability Guarantees
It’s an open-source project maintained by one person. Issues get triaged, releases ship, but there’s no SLA, no on-call, and no contact for production incidents. Fine for a side project, harder to put in a customer-facing flow.
If any of this sounds familiar, you’ve outgrown the library.
Option 2: A Hosted YouTube Transcript API
A hosted API is an HTTP endpoint someone else operates. You hit it with an API key, they return a transcript, you stop worrying about IP rotation. TubeAlfred runs one at api.tubealfred.com. Here’s what calling it looks like.
Authentication
API keys come in two flavors: ta_live_... for production, ta_test_... for development. Pass either as a Bearer token or X-API-Key header.
curl https://api.tubealfred.com/v1/youtube/video/dQw4w9WgXcQ/transcript \
-H "Authorization: Bearer ta_live_xxx"
Response
Successful responses use a consistent envelope:
{
"status": "success",
"data": {
"language": "English",
"language_code": "en",
"is_auto_generated": false,
"transcript": [
{
"text": "We're no strangers to love",
"start_ms": 18800,
"end_ms": 23500,
"start_time_text": "00:00:18.800"
}
],
"transcript_only_text": "We're no strangers to love. You know the rules and so do I...",
"available_tracks": [
{
"language_code": "en",
"language_name": "English",
"kind": "manual",
"is_translatable": true
}
]
}
}
Two things to notice. The segments use millisecond integers instead of float seconds, which is friendlier for downstream timing math. And there’s a transcript_only_text convenience field if you just need the flat string for an LLM prompt and don’t care about timing.
Language and Caption Kind
Two query params do most of the work.
# Get the Spanish track if it exists
GET /v1/youtube/video/VIDEO_ID/transcript?language=es
# Prefer manual captions, fall back to auto
GET /v1/youtube/video/VIDEO_ID/transcript?kind=any
# Only return if a manual track exists
GET /v1/youtube/video/VIDEO_ID/transcript?kind=manual
# Only return auto-generated captions
GET /v1/youtube/video/VIDEO_ID/transcript?kind=auto
The kind=manual pattern is useful when you’re processing transcripts for production content (summaries, search indexes) and want to silently skip videos where the caption quality is going to be bad.
The Fast Endpoint
There’s a second endpoint, /v1/youtube/video/{video_id}/transcript/fast, that returns the same shape with lower latency. Same price, same rate limit, recommended for most use cases. Use the standard endpoint if you need the full available_tracks metadata; use fast if you just need text.
Rate Limits and Headers
Every response includes:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1717945200
X-TubeAlfred-Team-ID: team_xxx
Burst is 60 requests per minute per key. Failed requests don’t count against credits.
Error Codes Worth Handling
| Code | Meaning | What to do |
|---|---|---|
| 401 | Invalid or missing API key | Check the Authorization header |
| 402 | Insufficient credits | Top up; response includes required vs available |
| 403 | Key lacks scope | Check key permissions in dashboard |
| 422 | Bad parameters | Fix the request payload |
| 429 | Burst limit exceeded | Back off, retry with jitter |
| 502 | YouTube upstream failed | Retry once, then surface the error |
The 402 is worth special handling. It tells you both what was required and what’s available, so you can decide whether to top up or fail gracefully.
Side-by-Side: When to Use Which
youtube-transcript-api library | Hosted REST API | |
|---|---|---|
| Cost | Free | $5/month for 3,500 shared credits; an available transcript uses 1 credit |
| Setup time | 3 lines of code | Sign up, copy key, make HTTP call |
| IP blocks | You own them | Handled upstream |
| Batch support | Sequential only | Manual loop, but no IP block ceiling |
| Rate limit visibility | None | Response headers |
| Reliability | Best effort | Managed service with retry/error paths |
| Schema drift | You patch and redeploy | Handled by the provider |
| Realistic ceiling | Low hundreds/day before blocks | Tens of thousands/day |
Use the library if: You’re prototyping, processing a small fixed set of videos, or running personal projects.
Use a hosted API if: You’re shipping a feature to customers, processing channels at scale, or you’ve already hit IP blocks once.
The migration takes about 20 minutes. The library’s response shape and the hosted API’s response shape are slightly different, but a small adapter function handles it.
# Adapter so the rest of your code doesn't change
def to_legacy_shape(api_response):
data = api_response["data"]
return [
{
"text": seg["text"],
"start": seg["start_ms"] / 1000,
"duration": (seg["end_ms"] - seg["start_ms"]) / 1000,
}
for seg in data["transcript"]
]
If you’re already on the library and tired of fighting blocks, the TubeAlfred Transcript API gives you a free tier of credits to test the migration.
Common Errors and Fixes
TranscriptsDisabled
The video has captions disabled by the creator, or no caption track exists at all. Confirm by checking the video in a browser. There is no programmatic fix. Skip and move on.
NoTranscriptFound for a specific language
You asked for languages=["es"] and there’s no Spanish track. Fall back to listing available tracks first, then picking the best match.
try:
transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=["es", "en"])
except NoTranscriptFound:
transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
# Take whatever is generated
transcript = next(iter(transcript_list)).fetch()
Could not retrieve a transcript after working fine yesterday
Classic IP block. Test the same call from a different network. If it works, you’ve been rate-limited. If it fails everywhere, YouTube probably changed the underlying format. Check the library’s GitHub issues, upgrade, or move to a hosted API.
HTTP 429 from the hosted endpoint
Burst limit. Back off and retry with exponential delay plus jitter. Don’t retry tighter than the X-RateLimit-Reset value.
Empty transcript array on a video that clearly has captions
Two causes. Either the video was just published and captions haven’t been processed (wait 10 minutes), or the available track is in a language you didn’t request. Call list_transcripts or available_tracks to confirm.
Frequently Asked Questions
Is there an official YouTube transcript API from Google?
No. The YouTube Data API v3 has a captions resource, but it’s gated behind OAuth tied to the video owner. You can’t pull captions for arbitrary public videos through it. The community has filled the gap with the Python library and hosted APIs.
Does the library work for YouTube Shorts?
Yes, Shorts are regular video IDs from the library’s perspective. The hosted API also returns transcripts for Shorts.
What about age-restricted or private videos?
Neither approach handles private videos without an authenticated YouTube session. Age-restricted videos work intermittently in the library and depend on cookie handling. For production, expect both to fail and design around it.
How accurate are auto-generated captions?
Word error rates vary widely. For native English speakers in clean audio, expect 90–95% accuracy. For accented speakers, music, or technical jargon, it can drop below 70%. Always check is_auto_generated and adjust downstream expectations.
Can I use this commercially?
The Python library is MIT licensed, so yes. YouTube’s TOS is the more relevant constraint. Re-publishing transcripts as your own content can run afoul of copyright. Using them for search, summarization, or internal tooling is generally fine. Talk to a lawyer if your use case is novel.
What does a hosted API cost?
New TubeAlfred Creator subscriptions are $5/month for 3,500 credits shared across its YouTube tools. Existing active subscribers keep their current plan. An available transcript uses 1 credit, while failed requests and unavailable captions are not charged. See TubeAlfred pricing for the current plan and operation costs.
Can I get just the text without timestamps?
The library returns segments only. The hosted API includes a transcript_only_text field with the flat string already joined. If you’re feeding an LLM, the second is one less line of code.
Next Steps
If you’re prototyping, install the library and ship something this afternoon. The Python tutorial walks through the common patterns.
If you’re already at scale or hitting blocks, the TubeAlfred Transcript API takes the same problem off your plate for the cost of a coffee per thousand transcripts. The free tier covers most prototypes.
Either way, the choice is reversible. Start with what unblocks you fastest.