YouTube Transcript API for Python: quick-start tutorial

YouTube Transcript API for Python: quick-start tutorial

By Nikhil KumarPublished March 20, 2026Last updated June 17, 202611 min read

If you search "youtube transcript api python," the first thing you'll find is the open-source youtube-transcript-api package on PyPI. It looks perfect. Install with pip, import, call a function, get a transcript.

Then you deploy it to a server. And it breaks.

YouTube changes something on its end. The library gets IP-blocked on your cloud instance. You wake up to a crashed pipeline with no support channel to call. I've seen this story more times than I can count. It's frustrating because the initial experience is so smooth.

There's a better approach. A managed REST API that works with any Python HTTP library and never breaks because of YouTube frontend changes. You don't install a scraping package. You make an HTTP request. It works today, and it'll work next month.

This tutorial gets you from zero to extracting YouTube transcripts in Python in under five minutes. Every code example is production-ready. Copy it into your project and go.

A shiny REST endpoint card beside a dusty package box, representing a REST API vs a PyPI package.
A hosted API beats a locally-maintained library at scale.

Why a REST API beats the PyPI package

The problem with open-source scraping libraries

The youtube-transcript-api Python package scrapes YouTube's internal endpoints. Those endpoints were never meant for public use. YouTube changes them whenever they want, with zero notice.

Here's what that means for your application:

  • The library breaks after YouTube updates. This happens several times a year, sometimes more.

  • Running it on a server (AWS, GCP, DigitalOcean, etc.) quickly leads to IP blocks and CAPTCHA challenges. YouTube's anti-bot systems are aggressive on datacenter IPs.

  • There's no SLA. Your production pipeline depends on a volunteer maintainer having time to push a fix.

  • It only handles transcripts. No search, no channel listings, no playlist support. If you need those later, you're adding another dependency.

For a hobby project or quick prototype? Sure, maybe fine. For anything you're shipping to users or running on a schedule? That's a reliability risk you don't need to take.

Coming from the open-source youtube-transcript-api library? Follow our step-by-step migration guide to switch in 15 minutes.

What a managed REST API gives you instead

TranscriptAPI is a hosted service that handles all the YouTube complexity on their servers, not yours:

  • Consistent, versioned API that doesn't break when YouTube changes its frontend

  • 49ms median response time. That's faster than most database queries.

  • Built-in rate limiting with clear headers so you always know your usage

  • Standard HTTP error codes with helpful messages

  • Works from any language or environment, not just Python

  • Seven endpoints covering transcripts, search, channels, and playlists

The tradeoff is cost. $5/month for 1,000 credits versus free for the open-source package. But if your time is worth anything at all, the managed API pays for itself after the first time it prevents a pipeline outage. One production incident costs more than a year of API credits.

A code block with five abstract lines and a green checkmark beside the final line.
Five lines between you and a usable transcript.

Quick start: your first transcript in 5 lines

Install dependencies and set up your key

You need the requests library. You almost certainly have it already, but just in case:

pip install requests

Set your API key as an environment variable:

export TRANSCRIPT_API_KEY="your_key_here"

That's the entire setup. Two commands. Compare that to setting up a Google Cloud Console project with OAuth consent screens and API quotas. This is simpler by an order of magnitude.

The minimal working example

Here are five lines of Python that extract a complete YouTube transcript:

import requests, os

headers = {"Authorization": f"Bearer {os.environ['TRANSCRIPT_API_KEY']}"}
response = requests.get("https://transcriptapi.com/api/v2/youtube/transcript",
                        params={"video_url": "dQw4w9WgXcQ"}, headers=headers)
data = response.json()
print(" ".join(seg["text"] for seg in data["transcript"]))

Run that. You'll see the full transcript printed to your terminal in about 50 milliseconds.

Let me walk through each line:

  1. Import -- requests for HTTP calls, os for the environment variable

  2. Auth header -- the API key goes in the Authorization header as a Bearer token

  3. GET request -- one call to the /youtube/transcript endpoint with the video URL

  4. Parse JSON -- the response body is JSON with a transcript array

  5. Print -- join all text segments into a single string

That's the core pattern. Everything else in this tutorial builds on top of it.

What the response looks like

{
  "video_id": "dQw4w9WgXcQ",
  "language": "en",
  "transcript": [
    {"text": "Never gonna give you up", "start": 0.0, "duration": 2.5},
    {"text": "Never gonna let you down", "start": 2.5, "duration": 2.3},
    {"text": "Never gonna run around and desert you", "start": 4.8, "duration": 3.1}
  ]
}

Each segment has text, start (seconds from the beginning of the video), and duration (how long that segment lasts). Clean, predictable, and always the same structure.

A branching JSON tree diagram with a root node and organized child nodes.
Every response is a predictable tree — parse it once, reuse forever.

Working with the response data

Parsing timestamps and segments

Raw seconds aren't very human-friendly. Here's a helper to convert them:

def format_time(seconds):
    """Convert seconds to HH:MM:SS or M:SS format."""
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = int(seconds % 60)
    if h > 0:
        return f"{h}:{m:02d}:{s:02d}"
    return f"{m}:{s:02d}"


# Print transcript with timestamps
for seg in data["transcript"]:
    timestamp = format_time(seg["start"])
    print(f"[{timestamp}] {seg['text']}")

Output:

[0:00] Never gonna give you up
[0:02] Never gonna let you down
[0:04] Never gonna run around and desert you

This format is handy for building video chapter markers, creating searchable transcripts with clickable timestamps, or generating study notes with time references.

Getting video metadata

Add send_metadata=true to your request to include the video title, author, and thumbnail URL:

BASE_URL = "https://transcriptapi.com/api/v2"

params = {
    "video_url": "dQw4w9WgXcQ",
    "send_metadata": "true"
}
response = requests.get(f"{BASE_URL}/youtube/transcript",
                        params=params, headers=headers)
data = response.json()

print(f"Title: {data['title']}")
print(f"Author: {data['author_name']}")
print(f"Language: {data['language']}")
print(f"Segments: {len(data['transcript'])}")

No extra credit cost for metadata. It comes free with the same single-credit transcript request. The author_url and thumbnail_url fields are also available, which is useful if you're building a UI that displays video information alongside the transcript.

Saving transcripts to files

Three output formats for three common use cases.

Plain text (best for LLM input, search indexing, and text analysis):

full_text = " ".join(seg["text"] for seg in data["transcript"])
with open("transcript.txt", "w", encoding="utf-8") as f:
    f.write(full_text)

Structured JSON (best for downstream processing and database storage):

import json

with open("transcript.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

SRT subtitles (best for video editing and accessibility tools):

def to_srt_time(seconds):
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = int(seconds % 60)
    ms = int((seconds % 1) * 1000)
    return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"


with open("subtitles.srt", "w", encoding="utf-8") as f:
    for i, seg in enumerate(data["transcript"], 1):
        start = to_srt_time(seg["start"])
        end = to_srt_time(seg["start"] + seg["duration"])
        f.write(f"{i}\n{start} --> {end}\n{seg['text']}\n\n")

Which format do you need? Most AI and NLP pipelines want plain text. Database-backed applications want JSON. Video editing workflows want SRT.

Multiple videos flowing through parallel processing lanes into transcript cards.
Do in parallel what the naive approach does in serial.

Batch processing: multiple videos

Sequential processing with a for loop

The simplest approach for small batches. Works fine for 10-50 videos:

import requests
import os
import time
import json

API_KEY = os.environ["TRANSCRIPT_API_KEY"]
BASE_URL = "https://transcriptapi.com/api/v2"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Read URLs from a file
with open("video_urls.txt") as f:
    video_urls = [line.strip() for line in f if line.strip()]

results = []
failed = []

for i, url in enumerate(video_urls):
    try:
        response = requests.get(
            f"{BASE_URL}/youtube/transcript",
            params={"video_url": url, "send_metadata": "true"},
            headers=HEADERS
        )

        if response.status_code == 200:
            data = response.json()
            results.append(data)
            title = data.get("title", "Unknown")
            print(f"[{i+1}/{len(video_urls)}] OK: {title}")
        else:
            failed.append({"url": url, "status": response.status_code})
            print(f"[{i+1}/{len(video_urls)}] FAIL ({response.status_code}): {url}")

    except Exception as e:
        failed.append({"url": url, "error": str(e)})
        print(f"[{i+1}/{len(video_urls)}] ERROR: {e}")

    time.sleep(0.05)  # 50ms delay between requests (optional)

print(f"\nDone: {len(results)} succeeded, {len(failed)} failed")

# Save results
with open("all_transcripts.json", "w") as f:
    json.dump(results, f, indent=2)

The time.sleep(0.05) between requests is polite but optional. TranscriptAPI handles 200-300 requests per minute depending on your plan. At 49ms per response, you could go significantly faster. But for a batch of 50 videos, the difference between 3 seconds and 5 seconds doesn't matter much.

Concurrent processing with asyncio and aiohttp

For larger batches (100+ videos), async processing is the way to go:

pip install aiohttp
import asyncio
import aiohttp
import os
import json
import time

API_KEY = os.environ["TRANSCRIPT_API_KEY"]
BASE_URL = "https://transcriptapi.com/api/v2"
MAX_CONCURRENT = 15  # 15 parallel requests is comfortable under rate limits


async def fetch_transcript(session, semaphore, video_url):
    """Fetch a single transcript with concurrency limiting."""
    async with semaphore:
        params = {"video_url": video_url, "send_metadata": "true"}
        headers = {"Authorization": f"Bearer {API_KEY}"}

        try:
            async with session.get(
                f"{BASE_URL}/youtube/transcript",
                params=params,
                headers=headers
            ) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    return {"url": video_url, "ok": True, "data": data}
                return {"url": video_url, "ok": False, "status": resp.status}
        except Exception as e:
            return {"url": video_url, "ok": False, "error": str(e)}


async def process_batch(urls):
    """Process all URLs with bounded concurrency."""
    semaphore = asyncio.Semaphore(MAX_CONCURRENT)
    start = time.time()

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_transcript(session, semaphore, url) for url in urls]
        results = await asyncio.gather(*tasks)

    elapsed = time.time() - start
    successes = [r for r in results if r["ok"]]
    failures = [r for r in results if not r["ok"]]

    print(f"Processed {len(urls)} videos in {elapsed:.1f}s")
    print(f"  OK: {len(successes)}, Failed: {len(failures)}")

    return results


# Load URLs and run
with open("video_urls.txt") as f:
    urls = [line.strip() for line in f if line.strip()]

results = asyncio.run(process_batch(urls))

# Save
with open("transcripts.json", "w") as f:
    json.dump([r["data"] for r in results if r["ok"]], f, indent=2)

Here's the performance difference in real numbers:

VideosSequentialConcurrent (15x)50~5 seconds~0.5 seconds100~10 seconds~1 second500~50 seconds~4 seconds1,000~100 seconds~8 seconds

For anything over 50 videos, the async approach saves real time. And the code isn't much more complex. The semaphore handles the hard part.

A shield deflecting error symbols with a fallback path curving around it.
Good error handling is a retry plus a fallback plus a log.

Error handling patterns

Retry logic with exponential backoff

Some errors are temporary. Server hiccups, brief rate limits, network blips. A retry usually fixes them.

import time
import random


def get_transcript_with_retry(video_url, max_retries=3):
    """Get transcript with automatic retry on transient errors."""
    retryable_codes = {408, 429, 500, 502, 503}

    for attempt in range(max_retries + 1):
        response = requests.get(
            f"{BASE_URL}/youtube/transcript",
            params={"video_url": video_url},
            headers=HEADERS
        )

        if response.status_code == 200:
            return response.json()

        if response.status_code not in retryable_codes:
            print(f"Permanent error {response.status_code} for {video_url}")
            return None

        if attempt < max_retries:
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f"Retry {attempt + 1}/{max_retries} in {wait:.1f}s")
            time.sleep(wait)

    print(f"Exhausted retries for {video_url}")
    return None

Key design decisions here:

  • Only retry on codes that might succeed next time (408, 429, 500, 502, 503)

  • A 401 or 404 won't fix itself. Don't waste time retrying those.

  • Random jitter prevents thundering herd problems when multiple workers retry at once

  • Three retries is usually enough. After that, something bigger is wrong.

Graceful degradation for batch jobs

For batch processing, one failure shouldn't kill the whole run:

def process_video_list(urls):
    """Process a list of videos with per-video error handling."""
    results = {"success": [], "failed": [], "errors": []}

    for url in urls:
        try:
            data = get_transcript_with_retry(url)
            if data:
                results["success"].append(data)
            else:
                results["failed"].append(url)
        except Exception as e:
            results["errors"].append({"url": url, "error": str(e)})

    total = len(urls)
    ok = len(results["success"])
    fail = len(results["failed"])
    err = len(results["errors"])
    print(f"\nBatch: {ok}/{total} OK, {fail} failed, {err} errors")

    return results

At the end, you know exactly what succeeded and what didn't. You can re-run just the failures without repeating the entire batch. That saves both time and credits.

Frequently asked questions

Can I use this alongside the youtube-transcript-api Python package?

They're completely separate tools. The youtube-transcript-api PyPI package is a local scraping library that runs on your machine. TranscriptAPI is a hosted REST API that you call over HTTP. You call TranscriptAPI with requests, httpx, aiohttp, or any HTTP library you like. No special package to install. No dependency that might break after a YouTube update.

If you're currently using the open-source package and running into reliability issues, switching to TranscriptAPI means changing a few lines of code. The data format is similar. The reliability is not.

How many credits does each transcript extraction cost?

One credit per successful transcript extraction. Period. Failed requests cost zero. The free tier gives you 100 credits to start. Paid plans begin at $5/month for 1,000 credits, with top-ups at $2.50 per 1,000 (monthly) or $1.50 per 1,000 (annual plan). The annual plan is $54/year.

Does the API work with Python 2?

TranscriptAPI is a REST endpoint, so any Python version that can make HTTP requests will work. The code examples in this tutorial use Python 3.8+ syntax (f-strings, asyncio, walrus operators), but the API itself has no Python version requirement. If you're still on Python 2 for some reason, urllib2 will work fine.

What about the httpx library?

Works great. Just swap requests.get for httpx.get. The API is standard HTTP, so any client library that supports GET requests with custom headers will work. I used requests in the examples because it's the most common choice. But httpx gives you async support built in, which means you can skip the aiohttp dependency for concurrent processing.

Can I get transcripts in languages other than English?

Yes. The API returns transcripts in whatever language is available for the video. Many videos have both manual and auto-generated captions in multiple languages. The language field in the response tells you which language you got. You can also request a specific language using the lang parameter.

What to build next

Getting YouTube transcripts in Python takes five lines of code with TranscriptAPI. No scraping libraries. No browser automation. No surprise breakages at 3 AM.

For production use, add the error handling and async concurrency patterns from this tutorial. They'll make your pipeline robust enough to handle thousands of videos reliably.

Start with 100 free credits at transcriptapi.com. Every code example in this tutorial works out of the box. Copy, paste, run.

Want to go deeper? The complete developer guide covers all seven API endpoints. The bulk extraction guide shows patterns for processing entire YouTube channels. And the error handling best practices digs into production edge cases you'll want to handle.

What will you build with transcript data in Python?

Meta description: Get YouTube transcripts in Python with 5 lines of code. This quick-start tutorial covers setup, API calls, error handling, and batch extraction patterns.

Want Node.js examples and deeper error handling? See our programmatic transcript extraction guide which covers multiple languages.

Frequently Asked Questions

What problems does the youtube-transcript-api PyPI package cause in production?
Three big ones. It scrapes YouTube's undocumented internal endpoints, which change without notice and break your pipeline several times a year. Deployed to a cloud server (AWS, GCP, DigitalOcean), it gets IP-blocked almost immediately because YouTube flags datacenter ranges aggressively. And it has no SLA — if it breaks while the volunteer maintainer is unavailable, your pipeline stays down. Fine for a hobby project, but a real reliability risk for anything serving users on a schedule.
How do I extract a YouTube transcript in Python with a managed REST API?
Use any HTTP client such as requests. Store your TranscriptAPI key in an environment variable, then send a GET request to the transcript endpoint with the video URL as a query parameter and the key in an Authorization Bearer header. The JSON response contains a transcript array, where each segment has the spoken text, its start time, and its duration — you read that array and pass it to whatever you're building. It's a handful of lines, with no scraping package to install or break.
Can I switch from the open-source library to a REST API without rewriting my pipeline?
Yes — it's mostly a find-and-replace, not a rewrite. Your downstream code for summarization, storage, and analysis stays the same because the response shape is similar: both return a list of segments with text, start time, and duration. You replace the library import and its single function call with one HTTP request, and the rest of the pipeline is untouched.
Share