# How do I download transcripts for hundreds of YouTube videos at once?

> Batch-download YouTube transcripts for 500 or 5,000 videos without 429 errors: where to get the video IDs, a resumable Python script, and what the whole run costs.

Canonical page: https://okdata.online/answers/bulk-youtube-transcripts/

**Short answer: **Put the video IDs in a list and call the YouTube transcript endpoint for each one with a few workers in parallel; okdata absorbs the YouTube side, so there is no IP to get banned. Each transcript costs $0.004, 1,000 videos come to $4.00 at most, and videos without captions are free.

A media researcher has a spreadsheet of 800 lecture videos and wants their transcripts in one file by Friday. With youtube-transcript-api, the run dies after a few dozen videos with 429 Too Many Requests, and the retry logic people add tends to hammer the same IP again. A batch this size needs three things: a clean list of IDs, a client that backs off politely, and a way to resume.

## Step 1: the list of IDs

- **You already have links.** Take the `v=` value, or the part after `youtu.be/` or `/shorts/`. The transcript endpoint wants the 11-character ID.
- **You have a topic.** [YouTube video search](/apis/youtube/videos/search/) returns about 20 videos a page with views and channel, so 100 candidates cost $0.02.
- **You have a channel.** The official Data API's `playlistItems.list` on the channel's uploads playlist costs 1 quota unit per page of up to 50 videos, which is the cheapest way to list a whole channel.

## Step 2: a resumable runner

```python
import json, os, time, requests
from concurrent.futures import ThreadPoolExecutor

URL = "https://api.okdata.online/v1/youtube/videos/transcript/get"
KEY = os.environ["OKDATA_API_KEY"]
OUT = "transcripts.jsonl"

def fetch(vid):
    while True:
        r = requests.get(URL, params={"id": vid}, timeout=90, headers={
            "Authorization": f"Bearer {KEY}", "Idempotency-Key": f"transcript-{vid}"})
        body = r.json()
        if body["code"] == "rate_limited":
            time.sleep(body.get("retry_after_seconds") or 2)
            continue
        return vid, body

ids = [line.strip() for line in open("video_ids.txt") if line.strip()]
done = {json.loads(l)["video_id"] for l in open(OUT)} if os.path.exists(OUT) else set()

with ThreadPoolExecutor(max_workers=4) as pool, open(OUT, "a", encoding="utf-8") as out:
    for vid, body in pool.map(fetch, [i for i in ids if i not in done]):
        if body["code"] == "ok":
            out.write(json.dumps(body["data"]["item"], ensure_ascii=False) + "\n")
        else:
            print(vid, body["code"])  # not_found = no captions, not charged
```

Run it again after any crash; finished videos are skipped. The `Idempotency-Key` means a retried call that already succeeded within 24 hours is replayed, not billed twice.

## What the run costs

| Batch | Cost at most |
|---|---|
| 100 videos | $0.40 |
| 800 videos | $3.20 |
| 1,000 videos | $4.00 |

Every captionless video and every error lowers the bill, since neither is charged. New keys start with 0.10 of credit, enough to test the script on a slice first.

## Limits

- Four workers is a sensible start. Your key's burst size is in the `X-RateLimit-Limit` header; `rate_limited` answers are free.
- No speech-to-text: videos without captions need Whisper on audio you may use.
- One language per call. See `lang` in the response.

## When to do it differently

If the batch is small and occasional, youtube-transcript-api from a home connection with a pause between calls usually gets through. For your own channel, the official API downloads your caption files. For the whole workflow, from search to summary, see the [video transcript solution page](/solutions/transcripts/).

## Endpoints used

- [YouTube video transcript](https://okdata.online/apis/youtube/videos/transcript/get/) `youtube.videos.transcript.get`: $0.004 per call
- [YouTube video search](https://okdata.online/apis/youtube/videos/search/) `youtube.videos.search`: $0.004 per call · about 20 videos per page

Updated 2026-11-12
