Back to blog
Tutorial·Jun 21, 2026·4 min read

How to Fetch YouTube Transcripts in Python

Two ways to pull YouTube transcripts in Python: the free youtube-transcript-api library for quick scripts, and a managed API for metadata, batching, and reliability at scale - with copy-paste code for both.

Chandler Caseyby Chandler Casey

There are two practical ways to get YouTube transcripts in Python: install a free open-source library and fetch captions yourself, or call a managed API that handles blocking, metadata, and scale for you. This guide shows both, with copy-paste code, and is honest about where each one breaks down.

Option 1: the free youtube-transcript-api library

The most popular free option is the youtube-transcript-api package. It talks to YouTube's caption endpoints directly, so there's no API key and no cost.

pip install youtube-transcript-api

Fetch a transcript by video ID:

from youtube_transcript_api import YouTubeTranscriptApi

# Classic API (0.6.x and earlier) - returns a list of {text, start, duration}
transcript = YouTubeTranscriptApi.get_transcript("dQw4w9WgXcQ")
text = " ".join(chunk["text"] for chunk in transcript)
print(text)

Version 1.x changed to an instance-based API. If get_transcript is missing, you're on the new one:

from youtube_transcript_api import YouTubeTranscriptApi

ytt = YouTubeTranscriptApi()
fetched = ytt.fetch("dQw4w9WgXcQ")
text = " ".join(snippet.text for snippet in fetched)

You can also request specific languages, falling back in order:

YouTubeTranscriptApi.get_transcript("dQw4w9WgXcQ", languages=["en", "en-US", "es"])

That's genuinely all you need for a quick script. The catch is what the library doesn't do:

  • It gets blocked from servers. YouTube tolerates requests from residential IPs but blocks datacenter ranges, so code that works on your laptop returns IpBlocked/TooManyRequests the moment it runs on AWS, a CI runner, or a Lambda. You then have to wire up rotating residential proxies yourself.
  • No metadata. You get caption chunks - no title, channel, duration, or thumbnail.
  • No discovery. You must already have the video IDs. There's no built-in way to list a channel, expand a playlist, or search.
  • No batching or retries. Fetching hundreds of videos means writing your own concurrency, backoff, and caching - and absorbing the breakage every time YouTube changes its internals.

For a one-off script or a notebook, this is the right tool. For anything that runs unattended or at scale, the maintenance cost adds up fast.

Option 2: a managed API

A managed transcript API moves the proxy pool, retries, caching, and metadata to someone else's servers. You make a normal HTTP request and get clean JSON back. The examples below use TranscriptFetch and the httpx client (requests works the same way).

pip install httpx

Set your key once and fetch a single transcript:

import os
import httpx

KEY = os.environ["TRANSCRIPTFETCH_KEY"]

res = httpx.post(
    "https://transcriptfetch.com/api/v1/transcripts/video",
    headers={"Authorization": f"Bearer {KEY}"},
    json={"video": "dQw4w9WgXcQ"},  # bare ID or any YouTube URL
    timeout=60,
)
res.raise_for_status()
data = res.json()["data"]

print(data["title"])
print(data["text"])               # full transcript as plain text
for seg in data["segments"][:3]:  # timestamped segments
    print(f'{seg["start"]:.1f}s  {seg["text"]}')

Every response shares one envelope - { ok, request_id, data, usage } - so the title, plain text, and timestamped segments come back together, and you can see credits spent in usage.

Fetch many videos in one call

The batch endpoint takes up to 50 IDs and returns one result per video, including a per-video outcome so a single missing transcript doesn't fail the whole job:

res = httpx.post(
    "https://transcriptfetch.com/api/v1/transcripts/batch",
    headers={"Authorization": f"Bearer {KEY}"},
    json={"videoIds": ["dQw4w9WgXcQ", "9bZkp7q19f0"]},
    timeout=120,
)
for item in res.json()["data"]["results"]:
    if item["outcome"] == "ok":
        print(item["video_id"], len(item["text"]), "chars")
    else:
        print(item["video_id"], "->", item["outcome"])  # e.g. no_transcript

Whole channels, playlists, and search

You don't need the IDs up front. List a channel (or playlist, or keyword search), then feed the IDs straight into a batch call:

def post(path, body):
    return httpx.post(
        f"https://transcriptfetch.com/api/v1/transcripts/{path}",
        headers={"Authorization": f"Bearer {KEY}"},
        json=body,
        timeout=180,
    ).json()["data"]

# 1) list the channel's videos (use next_cursor to page past 50)
listing = post("channel", {"channel": "@lexfridman", "limit": 50})
ids = [v["videoId"] for v in listing["videos"]]

# 2) fetch all of them in one batch
batch = post("batch", {"videoIds": ids[:50]})
print(f'{len(batch["results"])} transcripts fetched')

Swap "channel" for "playlist" ({"playlist": "PL…"}) or "search" ({"query": "how transformers work"}) - same shape, same flow.

Which should you use?

youtube-transcript-apiManaged API
CostFreeCredit-based (free tier to start)
Runs on servers/CIBlocked without your own proxiesYes - proxies handled
Video metadataNoTitle, channel, duration
Channel / playlist / searchNoYes
Batch fetchingBuild it yourselfOne call, up to 50
MaintenanceYou own breakage + retriesHandled

Use the free library for prototypes, notebooks, and small personal scripts. Reach for a managed API when transcripts feed something that has to keep working - a production app, a scheduled job, or a RAG/summarization pipeline pulling from whole channels.

If you want to try the managed path, TranscriptFetch has a free tier and one-credit-per-fetch pricing (failed and caption-less videos are free), plus an MCP server if you'd rather pull transcripts straight into Claude or Cursor. You can also test any single video, no code required, with the free YouTube transcript generator.