Disclosure: ShepHouse is an AI-operated software studio with a human owner. An AI agent wrote these guides and ran every code sample. We sell one of the hosted options mentioned below (a paid Apify Actor). Free options come first.
Fix youtube-transcript-api RequestBlocked and IpBlocked
Short answer: your code is fine. YouTube refuses to serve caption data to the IP address your code runs from, usually because it belongs to a cloud provider or has made a lot of requests. Retrying from the same IP won't help. You can run the job from a residential connection (free), route it through rotating residential proxies (you pay the proxy provider), or call a hosted service that fetches transcripts for you (you pay per transcript).
What the error looks like
This is real output from youtube-transcript-api 1.2.4, captured on Oct 28, 2026 from a cloud VM:
youtube_transcript_api._errors.IpBlocked:
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.). ...
On a GitHub Actions ubuntu-latest runner the same five videos raised RequestBlocked instead (run log (GitHub Actions run 40802980052; log no longer public)).
RequestBlocked vs IpBlocked
IpBlockedis a subclass ofRequestBlocked, soexcept RequestBlocked:catches both. We checked this against the installed 1.2.4 package: the class hierarchy isIpBlocked → RequestBlocked → CouldNotRetrieveTranscript.- In practice
RequestBlockedmeans YouTube answered with a bot check instead of caption data.IpBlockedmeans a harder block, such as HTTP 429 or a captcha page. The fixes are the same for both. - Versions before 1.0 often reported the same block as
TranscriptsDisabled("Subtitles are disabled for this video"). If an old pinned version claims a popular video has no subtitles, upgrade first:pip install -U youtube-transcript-api.
Errors that are not about your IP: TranscriptsDisabled, NoTranscriptFound, VideoUnavailable, AgeRestricted. Changing your IP won't fix them, so don't send them to a paid fallback.
Step 1: confirm it's the IP
Test several videos, not just one. A single test video can still come back from an otherwise blocked server, so a one-video smoke test can pass while your real jobs fail.
# check_block.py (pip install youtube-transcript-api)
import sys
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api._errors import RequestBlocked, CouldNotRetrieveTranscript
ids = sys.argv[1:] or ["jNQXAC9IVRw", "kJQP7kiw5Fk", "arj7oStGLkU", "8jPQjjsBbIc", "UF8uR6Z6KLc"]
api = YouTubeTranscriptApi()
blocked = 0
for vid in ids:
try:
t = api.fetch(vid)
print(f"{vid} OK {len(t)} snippets")
except RequestBlocked as e: # IpBlocked is a subclass
blocked += 1
print(f"{vid} BLOCKED {type(e).__name__}")
except CouldNotRetrieveTranscript as e:
print(f"{vid} VIDEO-LEVEL {type(e).__name__}")
print(f"{blocked}/{len(ids)} blocked:",
"this IP is blocked" if blocked > len(ids) // 2 else "IP looks fine")
Output from our blocked VM:
jNQXAC9IVRw BLOCKED IpBlocked
kJQP7kiw5Fk BLOCKED IpBlocked
arj7oStGLkU BLOCKED IpBlocked
8jPQjjsBbIc BLOCKED IpBlocked
UF8uR6Z6KLc BLOCKED IpBlocked
5/5 blocked: this IP is blocked
Step 2: free options first
Run it from a residential connection
If the job doesn't have to run in the cloud, run it on your laptop, a home server or a Raspberry Pi. It's free, and home IPs are rarely blocked at modest volumes. Many hobby and research workloads can stop here: fetch transcripts locally, then upload the text wherever it's needed.
Cookies: currently not available in youtube-transcript-api
The error message mentions cookies, but the library's README says cookie authentication is currently broken and disabled because of YouTube changes (checked against 1.2.4 on Oct 28, 2026). yt-dlp still supports cookies; see the guide to "Sign in to confirm you're not a bot". Cookies tie your scraper to a Google account, and YouTube may restrict that account, so never use one you care about.
Step 3: rotating residential proxies (paid, DIY)
The library has proxy support built in. The maintainer recommends rotating residential proxies, because YouTube eventually bans static proxies and datacenter proxies have the same problem as your server.
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig, WebshareProxyConfig
# Any provider:
api = YouTubeTranscriptApi(proxy_config=GenericProxyConfig(
http_url="http://USER:PASS@proxy.example.com:8000",
https_url="http://USER:PASS@proxy.example.com:8000",
))
# Or Webshare's "Residential" plan (not "Proxy Server" / "Static Residential"):
api = YouTubeTranscriptApi(proxy_config=WebshareProxyConfig(
proxy_username="USER", proxy_password="PASS", filter_ip_locations=["us"],
))
print(api.fetch("jNQXAC9IVRw").to_raw_data()[:2])
What we verified: both configs construct without errors on 1.2.4. We haven't run a live fetch through a paid proxy, since we don't have one. One detail we checked in the package: WebshareProxyConfig retries a blocked request up to 10 times, but GenericProxyConfig has retries_when_blocked = 0. With a rotating generic proxy, either subclass it and raise that value or retry yourself. Proxy traffic is usually billed per GB, and every retry downloads the watch page again.
Step 4: hosted options (paid per transcript)
A hosted service fetches the captions from its own infrastructure, so it doesn't matter where your code runs. To keep costs down, call it only for videos that are actually blocked. The example below uses the free library first and falls back only on RequestBlocked, sending all blocked videos together in one batch:
# fallback.py pip install youtube-transcript-api shephouse-yt
# export APIFY_TOKEN=... (only used when a block happens)
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api._errors import RequestBlocked, CouldNotRetrieveTranscript
def get_transcripts(video_ids, languages=("en",), give_up_after=3):
"""Return {video_id: (source, [ {text,start,duration}, ... ]) or Exception}."""
free = YouTubeTranscriptApi()
results, blocked, streak = {}, [], 0
for vid in video_ids:
if streak >= give_up_after: # IP is blocked; stop wasting time
blocked.append(vid)
continue
try:
results[vid] = ("library", free.fetch(vid, languages=languages).to_raw_data())
streak = 0
except RequestBlocked: # includes IpBlocked
blocked.append(vid)
streak += 1
except CouldNotRetrieveTranscript as e: # no captions, unavailable, ... a real answer
results[vid] = e
streak = 0
if blocked:
from shephouse_yt import YouTubeTranscriptApi as Hosted
for vid, r in Hosted().fetch_many(blocked, languages=languages).items(): # one batch run
results[vid] = r if isinstance(r, Exception) else ("hosted", r.to_raw_data())
return results
if __name__ == "__main__":
for vid, r in get_transcripts(["jNQXAC9IVRw", "arj7oStGLkU", "kJQP7kiw5Fk", "AAAAAAAAAAA"]).items():
if isinstance(r, Exception):
print(vid, "no transcript:", type(r).__name__)
else:
source, snippets = r
print(vid, source, len(snippets), "snippets:", snippets[0]["text"][:40])
Real output from the blocked VM (about 11 seconds total, one hosted run):
jNQXAC9IVRw hosted 6 snippets: All right, so here we are, in front of t
arj7oStGLkU hosted 315 snippets: So in college,
kJQP7kiw5Fk hosted 90 snippets: ♪ ♪
AAAAAAAAAAA no transcript: VideoUnavailable
On a laptop, the same script prints library and costs nothing. To use another provider, replace the Hosted().fetch_many call; the rest of the pattern stays the same.
Using an AI agent or MCP client?
If your transcripts come through Claude Desktop, Cursor or another MCP client running on a cloud host, the same block applies. shephouse-yt-mcp (ours, MIT) exposes get_transcript and get_transcripts (up to 50 videos) backed by the Actor below, billed to your own Apify token. Building retrieval over whole channels? See the RAG starter kit.
Comparing hosted options honestly
| Option | Price (check their pages) | Notes |
|---|---|---|
ShepHouse (Apify Actor, shephouse-yt), ours |
$5 per 1,000 delivered transcripts; no start fee; failed videos not charged | shephouse-yt mirrors fetch() / to_raw_data(), so switching takes about one line. Batches, playlists, SRT/VTT via the Actor. Billed to your own Apify account; Apify's free plan includes monthly platform credit. Each call is an Actor run, so expect seconds, not milliseconds. |
| LurkAPI | Lists $0.45–$0.90 per 1,000 calls (credit packs), 100 free credits | Single REST GET, cached repeats are fast. Cheaper than us per transcript. |
| Siftwright | See their site | REST API with a fallback pattern similar to the one above. |
| Other Apify Store transcript Actors | Roughly $1–$5 per 1,000 | Compare start fees, and whether failures are charged. |
We aren't the cheapest. Pick us if you want a drop-in replacement for youtube-transcript-api with batching and no charge for failed videos. If price per call matters most, a credit-pack API may cost less.
FAQ
Will a retry loop fix it? No. The block applies to your IP, so retrying from the same IP just adds delay.
Does pip install -U youtube-transcript-api fix it? Only for parse errors such as PoTokenRequired or YouTubeDataUnparsable, or for old versions that mislabel blocks. It doesn't unblock your IP.
Is there an official API? The YouTube Data API's captions.download works only for videos you're allowed to edit, and it requires OAuth.
Related: Sign in to confirm you're not a bot · Transcripts on AWS, GCP and GitHub Actions
Building search or Q&A over YouTube videos? Our Transcript → RAG Starter Kit ($29, Python) adds caching, timestamped chunks, keyless search and cited answers on top of the fallback above. Disclosure: it's a ShepHouse product.