# youtube-transcript-api says my IP is blocked. How do I get YouTube transcripts from a server?

> youtube-transcript-api raises RequestBlocked on AWS, GCP, Railway or Colab. Fetch YouTube transcripts over an HTTPS API instead, with no proxy pool: cost and code.

Canonical page: https://okdata.online/answers/youtube-transcript-ip-blocked/

**Short answer: **Stop fetching captions from YouTube with your server's own IP: call the YouTube transcript endpoint (/v1/youtube/videos/transcript/get) and okdata does the fetching. Each transcript costs $0.004, a video without captions returns not_found and costs nothing, and you need no residential proxies.

The pattern is always the same. A video summariser works on your laptop, you deploy it to AWS, Railway or a Colab notebook, and the first request fails with `RequestBlocked` or `IpBlocked`. YouTube refuses caption requests from many cloud IP ranges, and the library cannot change where your server sits. People in the project's issue tracker report blocks even after buying rotating proxies.

The way out is to stop asking YouTube from that IP at all. Your server calls an HTTPS API with a key; the API returns the transcript.

## The endpoint

[YouTube video transcript](/apis/youtube/videos/transcript/get/) takes the 11-character video ID (`id`) and an optional preferred language (`lang`). It returns the full text plus timed segments, each with `start`, `end` and `text` in seconds.

| Volume | Calls | Cost |
|---|---|---|
| 1 video | 1 | $0.004 |
| 100 videos | 100 | $0.40 |
| 1,000 videos | 1000 | $4.00 |

Those are ceilings: videos without captions come back as `not_found` and are not charged.

## A drop-in replacement for fetch()

```python
import os, requests

def fetch_transcript(video_id, lang=None):
    params = {"id": video_id, **({"lang": lang} if lang else {})}
    body = requests.get(
        "https://api.okdata.online/v1/youtube/videos/transcript/get",
        headers={"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"},
        params=params, timeout=60,
    ).json()
    if body["code"] == "not_found":
        return None  # the video has no captions; nothing was charged
    if body["code"] != "ok":
        raise RuntimeError(f"{body['code']}: {body['message']}")
    return body["data"]["item"]

t = fetch_transcript("dQw4w9WgXcQ", lang="en")
print(t["lang"], len(t["segments"]), t["text"][:200])
```

If your code expects youtube-transcript-api's shape, `duration` is `end - start`.

## Things that still bite

- Pass the ID, not the link. From `https://www.youtube.com/watch?v=dQw4w9WgXcQ`, send `dQw4w9WgXcQ`.
- A burst of calls can hit your key's rate limit. A `rate_limited` answer is free; wait `Retry-After` seconds and resend. Codes are listed under [errors](/docs/errors/).
- When a call times out, retry with the same `Idempotency-Key`; a finished result is replayed without a second charge.
- Only public videos with captions work. There is no speech-to-text here.

## When another route is better

- Keep youtube-transcript-api if you already pay for a residential proxy pool that works for you; at very high volume that can cost less.
- For a few dozen videos, running the library from a home connection usually works.
- For your own channel, the official YouTube Data API can download your caption files, because you have edit rights.
- For videos with no captions at all, run Whisper on audio you are allowed to download.

More batch patterns are on 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

Updated 2026-11-12
