yt-dlp/yt-dlp · error · ExtractorError
Invalid video password
Error message
Invalid video password
What it means
Same Loom GraphQL signal (__typename 'VideoPasswordMissingOrIncorrect') but a video password WAS supplied via get_param('videopassword') — meaning the password was submitted and still rejected: it is wrong (changed, typo'd, or mangled by shell/API encoding). Raised with expected=True as 'Invalid video password'.
Source
Thrown at yt_dlp/extractor/loom.py:371
return filter_dict({
'en': traverse_obj(subs_data, (
'data', 'fetchVideoTranscript',
('source_url', 'captions_source_url'), {
'url': {url_or_none},
})) or None,
})
def _real_extract(self, url):
video_id = self._match_id(url)
metadata = traverse_obj(
self._call_graphql_api('GetVideoSSR', video_id, 'Downloading GraphQL metadata JSON', fatal=False),
('data', 'getVideo', {dict})) or {}
if metadata.get('__typename') == 'VideoPasswordMissingOrIncorrect':
if not self.get_param('videopassword'):
raise ExtractorError(
'This video is password-protected, use the --video-password option', expected=True)
raise ExtractorError('Invalid video password', expected=True)
video_data = self._call_graphql_api(
'GetVideoSource', video_id, 'Downloading GraphQL video JSON')
chapter_data = self._call_graphql_api(
'FetchChapters', video_id, 'Downloading GraphQL chapters JSON', fatal=False)
duration = traverse_obj(metadata, ('video_properties', 'duration', {int_or_none}))
return {
'id': video_id,
'duration': duration,
'chapters': self._extract_chapters_from_description(
traverse_obj(chapter_data, ('data', 'fetchVideoChapters', 'content', {str})), duration) or None,
'formats': self._extract_formats(video_id, metadata, video_data),
'subtitles': self.extract_subtitles(video_id),
**traverse_obj(metadata, {
'title': ('name', {str}),
'description': ('description', {str}),
'uploader': ('owner', 'display_name', {str}),View on GitHub (pinned to 81ecd58b13)
Solutions
- Re-confirm the current password with the video owner (passwords are case-sensitive)
- Quote the value properly on the CLI: --video-password 'p@ss w0rd!'; in the Python API pass it as the videopassword option verbatim
- Check scripts for double-encoding (percent-signs, URL-encoding) applied before the value reaches yt-dlp
- Verify by opening the share URL in a browser and entering the same password
Example fix
# before (shell mangles the ! or spacing) VIDEO_PW=p@ss word! yt-dlp --video-password $VIDEO_PW <url> # after (single-quoted, exact value) yt-dlp --video-password 'p@ss word!' 'https://www.loom.com/share/abc123'
Defensive patterns
Strategy: retry
Type guard
def is_loom_bad_password(err) -> bool:
from yt_dlp.utils import ExtractorError
return isinstance(err, ExtractorError) and 'Invalid video password' in str(err) Try / catch
import yt_dlp
from yt_dlp.utils import ExtractorError
for pw in candidate_passwords: # e.g. current + last-rotated value
try:
with yt_dlp.YoutubeDL({'videopassword': pw}) as ydl:
info = ydl.extract_info(url, download=True)
break
except ExtractorError as e:
if 'Invalid video password' not in str(e):
raise
else:
ask_owner_for_current_password() Prevention
- Single-quote --video-password values on the shell to avoid ! and $ expansion
- Strip whitespace from programmatically sourced passwords before passing them
- When owners rotate passwords, update your stored value at the same time
When it happens
Trigger: Passing --video-password (or the videopassword option) whose value does not match the one the owner set on the Loom video; passwords with shell-special characters getting misquoted, or values accidentally URL-encoded twice when injected programmatically.
Common situations: Owner rotated the password; copy/paste with trailing whitespace; quoting bugs in scripts ($ or ! characters); password managers auto-filling a stale value.
Related errors
- This video is password-protected, use the --video-password o
- {self.IE_NAME} said: {expired}
- This video is not available yet. Release date: {start_date}
- {error}
- Content is not a video/podcast
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/ba4f4ce9f96453e1.
Report an issue: GitHub.