Python · Troubleshooting
TranscriptsDisabled, NoTranscriptFound, and CouldNotRetrieveTranscript
All three print 'Could not retrieve a transcript'. The class name is the part that tells you whether the video has no captions, you asked for the wrong language, or something else failed.
· 3 min read · YTAPI
youtube-transcript-api prints the same first line for almost every failure:
Could not retrieve a transcript for the video https://www.youtube.com/watch?v=...!
This is most likely caused by:The next line, and the class name, are what differ. If you catch CouldNotRetrieveTranscript and log only the message, an IP ban, a missing language, and a video with captions turned off all look identical. Catch the subclass, or print type(exc).__name__.
from youtube_transcript_api import YouTubeTranscriptApi
api = YouTubeTranscriptApi()
try:
transcript = api.fetch("dQw4w9WgXcQ", languages=["en"])
except Exception as exc:
print(type(exc).__name__)
print(exc)fetch asks for English unless you pass languages. That default is the source of a lot of NoTranscriptFound.
TranscriptsDisabled
Cause line: Subtitles are disabled for this video.
The library raises this when the video is playable and the player response has no caption tracks. Open the video and use "Show transcript". An empty panel matches this exception. A panel full of text means the track exists and this class is the wrong diagnosis. Look at the other names below.
What you can do about a video that really has no track is covered in what to do when a video has no captions. Short version: there is nothing to fetch. Speech-to-text on the audio is a different job, and you need the right to do it.
NoTranscriptFound
Cause line: No transcripts were found for any of the requested language codes, followed by the tracks that do exist, split into manually created and generated.
fetch(video_id) is fetch(video_id, languages=["en"]). A Spanish video with no English track hits this even though captions are sitting right there. The fix is to read the list and ask for a language that is on it:
transcript_list = api.list("dQw4w9WgXcQ")
print(transcript_list)
transcript = transcript_list.find_transcript(["es", "en"]).fetch()find_transcript checks manually created tracks first, then generated ones. find_generated_transcript and find_manually_created_transcript restrict that. en does not match en-GB. If you only want British English, ask for en-GB.
The message already contains the list. You usually do not need a second request to see it. Print the exception.
CouldNotRetrieveTranscript
This is the base class. TranscriptsDisabled and NoTranscriptFound are subclasses. So are the failures that have nothing to do with captions:
| Class | When the library raises it |
|---|---|
IpBlocked | YouTube returned HTTP 429, or a page with a reCAPTCHA |
RequestBlocked | The player said to sign in to confirm you are not a bot. IpBlocked is a subclass, so it is also a RequestBlocked |
AgeRestricted | The video needs a signed-in viewer. Cookie login is disabled in the current release |
VideoUnavailable | The video is gone, private, or removed |
InvalidVideoId | The id you passed is a URL. fetch wants the 11-character id |
PoTokenRequired | The caption URL carries exp=xpe. The library has no way to attach a PO token |
VideoUnplayable | Some other playability status, with the reason YouTube sent |
YouTubeRequestFailed | The HTTP call failed with a status other than 429 |
Catch order matters. IpBlocked has to come before RequestBlocked, and both before the base class.
from youtube_transcript_api._errors import (
CouldNotRetrieveTranscript,
IpBlocked,
NoTranscriptFound,
RequestBlocked,
TranscriptsDisabled,
)
try:
transcript = api.fetch(video_id, languages=["en"])
except TranscriptsDisabled:
print("no caption tracks")
except NoTranscriptFound as exc:
print(exc) # lists the languages that exist
except IpBlocked:
print("429 or captcha")
except RequestBlocked:
print("bot check or blocked IP")
except CouldNotRetrieveTranscript as exc:
print(type(exc).__name__)RequestBlocked and IpBlocked are the cloud-IP problem. Retrying them in a tight loop makes the block last longer. The platform-specific notes are AWS, Google Cloud, and Vercel, Render, and Railway.
The same three cases over HTTP
A hosted API should return a code for each case, so you are not parsing prose. On YTAPI a missing track is HTTP 404 with captions_disabled, and a language you asked for that the video does not have is 404 with language_not_found. Neither is billed. The default language list is ["*"]: the video's own language. ["en", "*"] asks for English first. Passing ["en"] with no fallback reproduces NoTranscriptFound on purpose.
curl -s -o /tmp/body -w "%{http_code}" \
https://api.ytapi.dev/v1/transcripts \
-H "Authorization: Bearer $YTAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"video_id": "dQw4w9WgXcQ", "languages": ["en"]}'The body on a 404 is {"error": {"code": "...", "message": "...", "retryable": false}}. Field reference: Extract transcript.
FAQ
Should I retry TranscriptsDisabled?
No. The creator turned captions off, and retrying won't change that. Mark the video and move on. The same goes for VideoUnavailable. RequestBlocked and IpBlocked are different: those are about your IP, not the video.
How do I see which transcript languages a video has?
With the library, YouTubeTranscriptApi().list(video_id) returns the available transcripts, both creator-made and auto-generated. Over HTTP, the free basic info endpoint returns the same list.
Why does CouldNotRetrieveTranscript only happen on my server?
On a server it's usually RequestBlocked or IpBlocked, both subclasses of it: YouTube refuses requests from cloud IP ranges. See AWS, Google Cloud, and Vercel, Render, and Railway.