If you've used youtube-transcript-api you've probably seen this. It works on your laptop. You deploy it to AWS or a VPS or some serverless thing, and every call dies with RequestBlocked or IpBlocked.
I've hit this a lot while building transcript scrapers. This week I reran it from scratch to get real numbers instead of forum guesses.
Why it works locally and fails on a server
YouTube decides per IP. The library's own README says it plainly: "YouTube has started blocking most IPs that are known to belong to cloud providers (like AWS, Google Cloud Platform, Azure, etc.)". A home connection looks like a person watching videos. A datacenter range doesn't.
The two errors mean slightly different things. I read the source of version 1.2.4 to check:
| Error | What YouTube did |
|---|---|
RequestBlocked |
The player API said LOGIN_REQUIRED with its bot-detection reason, the "Sign in to confirm you're not a bot" wall |
IpBlocked |
YouTube answered HTTP 429, or sent a reCAPTCHA page instead of the video page |
IpBlocked is a subclass of RequestBlocked, so except RequestBlocked catches both.
Reproducing it
Here's the smallest script that shows the problem:
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
transcript = ytt_api.fetch("jNQXAC9IVRw")
for snippet in transcript[:3]:
print(snippet.start, snippet.text)
From my home connection:
1.2 All right, so here we are, in front of the
elephants
5.318 the cool thing about these guys is that they
have really...
7.974 really really long trunks
I didn't spin up an EC2 box for this. I sent the same script through datacenter proxy IPs instead. They aren't AWS, but they're datacenter ranges, same as AWS. I also tried residential proxy IPs, and my home connection again. 10 tries each, on 5 different videos, with a new IP per try on the proxies:
| Where the request came from | Worked |
|---|---|
| My home connection | 10 of 10 |
| 10 datacenter IPs | 6 of 10, the other 4 raised RequestBlocked
|
| 10 residential IPs | 9 of 10, 1 raised IpBlocked
|
Honestly I expected the datacenter IPs to do worse. But failing 4 times out of 10 is useless for a job nobody is watching. And even residential IPs aren't perfect, which matters for the fix below.
Fix 1: run it from your own machine
If you have a few hundred videos and no deadline, just run it at home. Go slowly. The same README warns that a home IP gets blocked too if you send too many requests.
Fix 2: a rotating residential proxy
That's the README's own answer. The library ships WebshareProxyConfig for Webshare, and GenericProxyConfig for any other provider. Heads up, the Webshare links in that README are referral links, which the author says openly.
Here's the snag I hit. GenericProxyConfig doesn't retry. Its retries_when_blocked is 0, and it keeps the connection alive, so with a rotating proxy you can keep landing on the same blocked IP. Only WebshareProxyConfig retries, 10 times by default. With any other provider, retry yourself and build a new client each time:
import os
from youtube_transcript_api import YouTubeTranscriptApi, RequestBlocked
from youtube_transcript_api.proxies import GenericProxyConfig
PROXY_URL = os.environ["PROXY_URL"] # rotating residential proxy: http://user:pass@host:port
def fetch(video_id, tries=3):
for attempt in range(tries):
# a new client opens a new connection, so a rotating proxy gives it a new IP
ytt_api = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(http_url=PROXY_URL, https_url=PROXY_URL)
)
try:
return ytt_api.fetch(video_id)
except RequestBlocked: # IpBlocked is a subclass, so this catches both
if attempt == tries - 1:
raise
print(fetch("jNQXAC9IVRw")[0].text)
All right, so here we are, in front of the
elephants
Residential proxies bill by traffic, so check what each video costs you. The library loads the full watch page before it asks for captions. For the three videos I checked, that page was 310 to 370 KB compressed.
Fix 3: yt-dlp
yt-dlp can grab just the subtitles, without the video:
yt-dlp --skip-download --write-subs --write-auto-subs --sub-langs en --sub-format vtt -o "%(id)s.%(ext)s" "https://www.youtube.com/watch?v=jNQXAC9IVRw"
WEBVTT
Kind: captions
Language: en
00:00:01.200 --> 00:00:03.360
All right, so here we are, in front of the
elephants
From home it took 3 seconds with version 2026.08.19. Through 5 datacenter IPs, 3 failed with this:
ERROR: [youtube] jNQXAC9IVRw: Sign in to confirm you’re not a bot. Use --cookies-from-browser or --cookies for the authentication. See https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp for how to manually pass cookies. Also see https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies for tips on effectively exporting YouTube cookies
Same wall, so switching to yt-dlp doesn't get you around the IP problem. It needs a residential proxy on a server too.
The error tells you to pass cookies. I wouldn't do that with a real account. yt-dlp's own wiki says "By using your account with yt-dlp, you run the risk of it being banned (temporarily or permanently)."
This article was created with the help of AI. An AI agent (Claude) ran every test above on my machine on 25 Oct 2026 and drafted the text from those runs, and every output block is copied from them.
Top comments (0)