Dev.to AI 🤖 Ai 👁 0 📖 5 min read

How to Get YouTube Transcripts in Python for Your AI App (No API Key)

Disclosure: I built the YouTube Transcript API used in this guide and earn from its usage on Apify and RapidAPI. YouTube is one of the biggest piles of expert knowledge on the internet: conference talks, lectures, podca

Disclosure: I built the YouTube Transcript API used in this guide and earn from its usage on Apify and RapidAPI.

YouTube is one of the biggest piles of expert knowledge on the internet: conference talks, lectures, podcasts, product reviews. Almost all of it has captions, and captions are text. That makes YouTube a great source for search, summaries, and retrieval (RAG) in an AI app.

The official route is awkward. The YouTube Data API's caption download needs OAuth and only works for videos you own or manage, so it does not help for other people's videos.

This guide uses the YouTube Transcript API Actor on Apify instead. It reads the captions YouTube already shows, manual or automatic, and returns them as plain text plus timestamped segments. You need a free Apify account and its API token. You don't need a YouTube API key or a Google login.

Step 1: Get one transcript

from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("apisamurai/youtube-transcript-api").call(run_input={
    "videoUrls": ["https://www.youtube.com/watch?v=zjkBMFhNj_g"],
    "languages": ["en"],
    "includeSegments": True,
})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())

Each video becomes one row. Shortened:

{
  "videoId": "zjkBMFhNj_g",
  "title": "[1hr Talk] Intro to Large Language Models",
  "channelName": "Andrej Karpathy",
  "language": "en",
  "isGenerated": true,
  "matchedRequestedLanguage": true,
  "durationSeconds": 3588,
  "segmentCount": 1704,
  "text": "hi everyone so recently I gave a 30-minute talk on large language models ...",
  "segments": [
    { "text": "hi everyone so recently I gave a", "start": 0.16, "duration": 4.08, "end": 4.24 }
  ]
}

Prefer a plain REST call with an API key header instead of a client library? The same transcripts are available as a YouTube Transcript API on RapidAPI, with a free plan of 50 calls a month: one GET request with video=<link or id> returns the same fields.

languages is a preference list: en also matches regional tracks like en-US, and manual captions win over automatic ones. If none of your languages exist, the original caption language comes back with matchedRequestedLanguage: false (turn off fallbackToOriginal to skip those videos instead). Captions are never machine translated. If you want ready-made subtitle files too, set includeSrt or includeVtt.

Step 2: Chunk it, keeping the timestamps

A one-hour talk is too long to treat as one block, and you want every answer to point back to a moment in the video. So group the segments into windows of about two minutes and keep each window's start time:

video = rows[0]
print(f"{video['title']} ({video['channelName']})")
print(f"{video['durationSeconds'] // 60} min, {len(video['text'].split()):,} words, "
      f"{video['segmentCount']:,} segments, auto captions: {video['isGenerated']}")

def chunk(segments, seconds=120):
    """Group caption segments into windows of about `seconds`, keeping the start time."""
    chunks, current, start = [], [], None
    for seg in segments:
        if start is None:
            start = seg["start"]
        current.append(seg["text"])
        if seg["end"] - start >= seconds:
            chunks.append({"start": start, "text": " ".join(current)})
            current, start = [], None
    if current:
        chunks.append({"start": start, "text": " ".join(current)})
    return chunks

chunks = chunk(video["segments"])
print(f"{len(chunks)} chunks of about 2 minutes")

Output for Andrej Karpathy's "Intro to Large Language Models" (run on 7 November 2026):

[1hr Talk] Intro to Large Language Models (Andrej Karpathy)
59 min, 12,151 words, 1,704 segments, auto captions: True
31 chunks of about 2 minutes

Step 3: Search and jump to the exact moment

With timestamps on every chunk, a search result can link straight to the second it was said:

def stamp(seconds):
    m, s = divmod(int(seconds), 60)
    return f"{m:02d}:{s:02d}"

def link(video_id, seconds):
    return f"https://youtu.be/{video_id}?t={int(seconds)}"

def search(chunks, query):
    for c in chunks:
        i = c["text"].lower().find(query.lower())
        if i >= 0:
            snippet = c["text"][max(0, i - 40): i + 50]
            print(f"{stamp(c['start'])}  {link(video['videoId'], c['start'])}  ...{snippet}...")

search(chunks, "scaling laws")
25:41  https://youtu.be/zjkBMFhNj_g?t=1541  ...e language model space are what we call scaling laws it turns out that the performance of ...

Swap the keyword search for embeddings and a vector store when you have many videos; the chunk and timestamp structure stays the same.

Step 4: Give it to an LLM, with citations

For a single video you often don't need retrieval at all. Prefix each chunk with its timestamp and send the whole thing as context:

context = "\n\n".join(f"[{stamp(c['start'])}] {c['text']}" for c in chunks)
print(f"{len(context):,} characters of context, about {len(context) // 4:,} tokens")
print(context[:160])
64,630 characters of context, about 16,157 tokens
[00:00] hi everyone so recently I gave a 30-minute talk on large language models just kind of like an intro talk um unfortunately that talk was not recorded but

(The token count is the rough four-characters-per-token rule, close enough for planning.) About 16,000 tokens fits in one prompt for most current models. Ask the model to answer only from the transcript and to cite the [mm:ss] stamps, then turn the stamps into youtu.be links with link() above. You get answers people can check in one click.

One thing to notice in that output: automatic captions have no punctuation or capitals, and they can mishear names and technical words. Models handle that fine for summaries and Q&A, but if you quote the text, check it against the video.

Step 5: A whole channel at once

To build a knowledge base from a channel, list its videos first with the YouTube Scraper, then send all the links to the transcript Actor in one run:

listing = client.actor("apisamurai/youtube-scraper").call(run_input={
    "channels": ["@AndrejKarpathy"],
    "maxResultsPerSource": 20,
})
urls = [v["url"] for v in client.dataset(listing["defaultDatasetId"]).iterate_items()]

run = client.actor("apisamurai/youtube-transcript-api").call(run_input={
    "videoUrls": urls,
    "languages": ["en"],
    "includeSegments": True,
})
transcripts = list(client.dataset(run["defaultDatasetId"]).iterate_items())

One run takes up to 1,000 video links. Videos without captions are skipped and listed in the run's SUMMARY record, so you can see why each one was left out.

To keep the knowledge base fresh, save both inputs as tasks in Apify Console and schedule them weekly (the YouTube Scraper's publishedAfter setting limits the listing to recent uploads).

What it costs

Actor Price
YouTube Transcript API $3.50 per 1,000 successful transcripts, same on every plan
YouTube Scraper $1 per 1,000 videos listed

So the 20-video channel example comes to about 9 cents (2 cents for the listing, 7 cents for the transcripts). Videos without captions cost nothing.

Limits worth knowing

  • Only videos that have captions. The Actor reads existing captions; it does not run speech recognition.
  • No translation: you get the languages the video actually offers (availableLanguages lists them).
  • Automatic captions can contain recognition mistakes, as above.
  • Use transcripts responsibly: respect YouTube's terms and creators' rights, especially if you republish text.

The full field list and FAQ are on the YouTube Transcript API page.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.