Blog / Tutorials / Python YouTube Transcript API Tutorial
Tutorials 6 min read â€ĸ November 13, 2026

Python YouTube Transcript API Tutorial

Founder

You can fetch a YouTube transcript with Scriptube's API in about 15 lines of Python, using nothing but the requests library. This walks through the short version, then how to paginate once a batch has more items than fit on one page.

There's no dedicated Scriptube Python SDK, and you don't need one — the API is plain JSON over HTTPS, and requests covers everything: sending the API key header, serializing the request body, and parsing the response. If you're coming from scraping YouTube's own page directly, the difference is that this always returns a transcript (when one exists) as structured data, not HTML you have to pick apart.

Requirements

  • A Pro+ API key (X-API-Key header) — see plans.
  • Python 3.8+ and pip install requests.
  • A virtual environment is optional but recommended if this is going into a larger project: python -m venv .venv && source .venv/bin/activate before the pip install.

Create a key from your dashboard's API keys page, or programmatically via POST /api/v1/keys — see key scopes, rotation, and revocation if you're generating keys from code rather than copy-pasting one.

The 15-line version

POST /api/v1/transcripts/resolve is the fastest path for a script: videos Scriptube already has come back inline, anything new gets a batch_id to check later.

import requests

HEADERS = {"X-API-Key": "YOUR_API_KEY"}
BASE = "https://scriptube.me"

r = requests.post(f"{BASE}/api/v1/transcripts/resolve", headers=HEADERS,
                   json={"urls": ["https://youtu.be/dQw4w9WgXcQ"]})
r.raise_for_status()
data = r.json()

for video in data.get("transcripts", []):
    print(video["video_id"], "->", (video["transcript"]["text"] or "")[:80])

if data.get("batch_id"):
    print("queued for processing:", data["batch_id"])

If the video was already cached, you get the text immediately in transcripts. If not, batch_id is set and the video is processing — poll GET /api/v1/transcripts/{batch_id} until status is completed, the same as any other batch. For the always-async version of submit, see the full API guide.

What each line is doing, in case you're adapting it: HEADERS carries your key on every call. The json= argument serializes the body and sets the content-type header for you — no need to do that by hand. r.raise_for_status() turns a 4xx/5xx response into a Python exception immediately, so a bad request fails loudly instead of silently returning garbage three lines later. data.get("transcripts", []) defaults to an empty list rather than raising a KeyError when every video in the request was new and nothing came back inline.

Saving results to files

Once a batch is done, export it straight to disk instead of reconstructing files from the JSON yourself (add import time alongside requests for the sleep below):

def download_when_ready(batch_id, fmt="txt", out_path="transcripts.txt"):
    while True:
        status = requests.get(f"{BASE}/api/v1/transcripts/{batch_id}", headers=HEADERS).json()["status"]
        if status in ("completed", "partial", "failed", "cancelled"):
            break
        time.sleep(5)

    export = requests.get(
        f"{BASE}/api/v1/transcripts/{batch_id}/export",
        headers=HEADERS,
        params={"format": fmt},
    )
    export.raise_for_status()
    with open(out_path, "wb") as f:
        f.write(export.content)
    return status

Swap fmt for srt, vtt, csv, json, or jsonl depending on what you need next — see the export schema reference for exact field names in the structured formats.

Paginating a large batch

A batch poll returns at most 50 items per page by default. Once a batch has more than that, loop on skip/limit until has_more is false:

# Paginate a large finished batch 50 items at a time
skip = 0
while True:
    page = requests.get(f"{BASE}/api/v1/transcripts/{data['batch_id']}", headers=HEADERS,
                         params={"skip": skip, "limit": 50}).json()
    for item in page["items"]:
        print(item["video_id"], item["status"])
    if not page["has_more"]:
        break
    skip += 50

This is the same pattern a 2,000-video channel batch on the Unlimited plan needs — see pagination for large batches for the full breakdown, including how it differs from paginating the list of batches themselves.

Handling the common errors

Wrap calls in a check for the error envelope instead of assuming 200:

if r.status_code == 402:
    print("not enough credits:", r.json()["message"])
elif r.status_code == 429:
    code = r.json()["code"]  # e.g. rate_limited
    print("throttled:", code, "retry after", r.headers.get("Retry-After"))
elif not r.ok:
    print("error:", r.json())

A missing caption language doesn't error — the item still comes back with whatever track was available, plus a language_hint explaining the fallback. Check served_language against what you requested if your script needs to know whether it actually got the language it asked for, rather than assuming a 200 means an exact match.

A note on retries

If a network error means you genuinely don't know whether your /resolve call billed, don't just retry with a fresh request — generate a UUID once and send it as an Idempotency-Key header on both attempts. A replay of the same key returns the original result instead of creating (and charging for) a second batch. See safe retries with Idempotency-Key for the full pattern; the plain requests.post calls above don't need it for a first attempt, only for a retry after an ambiguous failure.

Running it as a scheduled script

For a daily job — download yesterday's uploads from a channel, for example — wrap the submit-and-poll pattern above in a plain function and run it from cron or a scheduled task runner. Keep the polling interval reasonable (5–10 seconds, not tight-looped) since reads are capped at 60 requests/minute per key; a script polling one batch doesn't come close to that, but a script polling many batches in a tight loop can. For anything that runs continuously rather than once a day, a registered webhook removes the polling loop entirely.

Next steps

Full reference: API documentation.

Try Scriptube Free

Extract YouTube transcripts instantly. No credit card required.

Get Started

Related Articles

Tutorials

Zapier, Make, and Pipedream: Connecting YouTube Transcripts Without a Native App

Scriptube does not have a native Zapier app, Make.com app, or Pipedream integration — say that plainly, because searching for "Scriptube Zapier integration"...

Tutorials

Send YouTube Transcripts to Google Sheets Automatically

Getting a new row in Google Sheets every time Scriptube finishes a transcript takes one webhook and one of two receivers: a Make scenario, or a small Google...

Tutorials

YouTube Transcripts in Excel or Google Sheets

Scriptube's CSV export already has everything a spreadsheet needs: title, channel, status, and the transcript text in plain columns. You don't need to reformat...