Fix youtube-transcript-api RequestBlocked and IpBlocked on servers
The script worked yesterday. It fetched a transcript in half a second on your laptop, you wired it into a worker, deployed it, and now every call ends with this:
RequestBlocked:
Could not retrieve a transcript for the video https://www.youtube.com/watch?v=jNQXAC9IVRw!
This is most likely caused by:
YouTube is blocking requests from your IP. This usually is due to one of the following reasons:
- You have done too many requests and your IP has been blocked by YouTube
- You are doing requests from an IP belonging to a cloud provider (like AWS, Google Cloud Platform, Azure, etc.). ...
That is the real output of youtube-transcript-api 1.2.4, produced on 20 October 2026 from a cloud container, for the first YouTube video ever uploaded. Nothing is wrong with your code. This post explains what the error means, which fixes work, which ones to avoid, and shows a small, tested fallback you can drop into an existing pipeline.
What RequestBlocked and IpBlocked mean
The library is open source and does not use an official YouTube API. It makes the same requests a browser makes when it plays a video: it asks YouTube for the player data, finds the caption tracks in the response, and downloads the one you asked for. YouTube answers those requests differently depending on where they come from.
Both exceptions live in youtube_transcript_api._errors, and they are related:
RequestBlockedis the general case: YouTube refused to serve the player data to your address.IpBlockedis a subclass ofRequestBlocked. You can catchRequestBlockedand cover both.
The library's own error text names the two usual causes. You made a lot of requests from one address, or your address belongs to a cloud provider. Home and office connections are rarely blocked. Datacenter ranges (AWS, GCP, Azure and most VPS hosts) frequently are. That is why the same code passes on a laptop and fails the moment it is deployed.
Two practical consequences:
- Retrying does not help. We ran three attempts, two seconds apart, from the blocked container. All three raised
RequestBlocked. A block is a property of the address, not a transient glitch, so a retry loop only adds latency. - A block hides the real answer. When YouTube refuses you, the library cannot tell you whether the video has captions at all. A video that does not exist also raises
RequestBlockedfrom a blocked address. You learn about the video only after you get past the block.
Not every failure is a block. The same module defines TranscriptsDisabled, NoTranscriptFound, VideoUnavailable, AgeRestricted, PoTokenRequired and others. Those describe the video or the request, and switching IPs will not change them. Your error handling should treat the two groups differently, which the fallback code below does.
Your options, honestly compared
1. Run it somewhere that is not a datacenter
If this is a personal script, run it on your own machine or a home server. It is free and it works. It stops being an answer when the job needs to run on a schedule in the cloud, or when more than one person's work depends on it.
2. Route requests through a proxy
The library has first-class proxy support. WebshareProxyConfig takes a username and password for residential proxies, and GenericProxyConfig takes any HTTP or HTTPS proxy URL:
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
api = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(
http_url="http://user:pass@proxy.example.com:8000",
https_url="http://user:pass@proxy.example.com:8000",
)
)
snippets = api.fetch("jNQXAC9IVRw").snippets
We did not run this one, because we have no proxy to point it at. The signature above is from the installed library. The caveats are worth knowing before you buy anything. The proxy has to be residential or otherwise not on a blocked range, since a datacenter proxy has the same problem as your server. You pay for it by traffic or by IP. And you take over the monitoring: when the pool gets flagged, your jobs fail until you notice.
3. Authenticate with cookies
The library's own error message mentions that authenticating requests with cookies can keep things working for a while, and in the same breath warns that YouTube may eventually permanently ban the account used. Do not tie an account you care about to a scraper.
4. Call an API that does the proxying for you
This moves the problem to a service whose whole job is to keep requests working. You send a video URL and get the transcript back as JSON, from any server. The tradeoff is cost: you pay per result instead of paying for proxy traffic. Whether that is cheaper depends on your volume. A few hundred transcripts a month is usually cheaper through an API than through a proxy plan you have to configure and watch. Millions a month is a different calculation, and at that scale you should price both.
We run one of these: the Siftwright YouTube Transcript API, and a free transcript tool in the browser for one-off videos. We are biased, so here is the useful part regardless of whose API you pick: the pattern below works with any provider.
The pattern: use the library first, fall back only on blocks
You do not have to rip out a working library to fix a blocked deployment. A good compromise is to keep the free path as the default and call the paid path only when YouTube blocks you. That keeps the cost proportional to the problem.
The rules for the wrapper:
- Fall back only on
RequestBlocked(which includesIpBlocked). - Do not fall back on
TranscriptsDisabled,NoTranscriptFoundorVideoUnavailable. The answer is "no", and a second service will say the same thing and may charge you to say it. - Return which path produced the text, so you can see in your logs how often you are blocked.
import os
import requests
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api._errors import (
IpBlocked,
NoTranscriptFound,
RequestBlocked,
TranscriptsDisabled,
VideoUnavailable,
)
API = "https://siftwright.com/v1/youtube-transcript"
KEY = os.environ["SIFTWRIGHT_API_KEY"]
def via_library(video_id):
snippets = YouTubeTranscriptApi().fetch(video_id, languages=["en"]).snippets
return " ".join(s.text for s in snippets)
def via_api(video_id):
r = requests.post(
API,
headers={"Authorization": f"Bearer {KEY}"},
json={
"urls": [f"https://www.youtube.com/watch?v={video_id}"],
"outputFormats": ["text"],
},
timeout=300,
)
r.raise_for_status()
item = r.json()["results"][0]
if item["status"] != "ok":
raise LookupError(item.get("error", item["status"]))
return item["text"]
def get_transcript(video_id):
"""Return (source, text). Only network blocks fall back."""
try:
return "library", via_library(video_id)
except (RequestBlocked, IpBlocked):
return "api", via_api(video_id)
except (TranscriptsDisabled, NoTranscriptFound, VideoUnavailable) as err:
raise LookupError(type(err).__name__) from err
for vid in ["jNQXAC9IVRw", "dQw4w9WgXcQ", "aaaaaaaaaaa"]:
try:
source, text = get_transcript(vid)
print(vid, source, len(text.split()), "words:", text[:50])
except LookupError as err:
print(vid, "no transcript:", err)
Install with pip install youtube-transcript-api requests. We ran exactly this file from the same blocked container with a real key:
jNQXAC9IVRw api 39 words: All right, so here we are, in front of the elephan
dQw4w9WgXcQ api 487 words: [♪♪♪] ♪ We're no strangers to love ♪ ♪ You know th
aaaaaaaaaaa no transcript: This video is unavailable
Read the output carefully, because it shows the pattern working as designed. The first two videos were blocked by the library, so both went through the API (api). On a laptop they would print library and cost nothing. The third ID does not exist. The library was blocked on it, so it fell back, and the API gave the real answer: This video is unavailable.
That call is also a small proof of the billing rule. The request contained two URLs in an earlier test, one real and one dead, and the response reported "count": 2, "billed": 1. Videos that return no transcript are not billed.
Doing it in bulk
If you are fetching many videos, do not call the fallback once per video. The API accepts a list of URLs in urls and returns one result per video, so you can send the blocked ones in a single request. A simple structure:
- Loop over IDs with the library, collecting the ones that raise
RequestBlocked. - Stop trying the library once the first few calls in a run are all blocked. If three in a row fail, the address is blocked and the rest will fail too. Send everything remaining straight to the API.
- Send the collected IDs in batches to the API with
outputFormatsset to["text"](or["text", "segments"]if you need timestamps).
Step 2 matters more than it looks. In our test a blocked attempt took under a second, which sounds harmless until you multiply it: 1,000 videos against a blocked address is more than ten minutes of guaranteed failures before the real path even starts.
For the full response shape, languages, SRT and VTT output, and playlist handling, see our YouTube transcript API guide for Python and Node.js and the API docs.
Other things worth checking before you blame the IP
A few errors look similar but have a different cause:
TranscriptsDisabledorNoTranscriptFound: the creator turned captions off, or no track exists in the language you requested. Check thelanguageslist, and consider asking for a fallback language. No proxy fixes this.AgeRestricted: the video is age-gated, so anonymous requests cannot read it. Skip it or handle it manually.PoTokenRequiredandYouTubeDataUnparsable: YouTube changed what its player returns, and the library needs an update. Runpip install -U youtube-transcript-apibefore anything else, and pin a version in production so an upgrade is a choice you make.- Works for some videos only: that is a hint that the failing ones have a video-level restriction rather than an address problem.
Choosing, in one paragraph
If you run it yourself, on your own connection, at low volume: keep using the library and do nothing else. If it must run in the cloud and you have a few hundred transcripts a month, use the fallback pattern above so you only pay when you are blocked. If YouTube transcripts are central to your product and your volume is large, price a managed proxy pool against a per-result API with your real numbers before deciding. And in every case, handle the "genuinely no captions" errors separately, because they are answers and not failures.
If you want to try the API path without writing code first, the free transcript tool runs the same extraction in your browser. For code, plans start at $29/month with 10,000 results included.
Related guides
How to get YouTube transcripts with an API (Python, Node.js and curl)
A practical guide to fetching YouTube transcripts from code: why DIY scrapers get IP-blocked on servers, working Python, Node.js and curl examples, languages and translation, SRT/VTT, playlists, and chunking transcripts for LLMs.
Turn a list of company websites into contact details: emails, phones and socials in Python
Enrich a lead list with public emails, phone numbers and LinkedIn/X links scraped from each company's own site, then rank the emails so you never write to the legal or privacy mailbox. Tested on real sites, including the messy results.
Get the next guide by email
Practical tutorials on transcripts, screenshots, news data and agent tooling. A couple of emails a month at most.