yt-dlp/yt-dlp · error · ExtractorError
{code}: {error_id}: {message}
Error message
{code}: {error_id}: {message} What it means
Thrown by StreaksBaseIE as a catch-all for STREAKS API errors: HTTP 403/404 whose JSON body carries any 'code' or 'message' other than the special-cased REQUEST_FAILED(124/126) and MEDIA_NOT_FOUND. The message is built with join_nonempty(code, error_id, message), so it reads like 'RATE_LIMITED: 77: too many requests'. Not marked expected, so yt-dlp treats it as an extraction failure.
Source
Thrown at yt_dlp/extractor/streaks.py:56
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, live_from_start=False):
try:
response = self._download_streaks_playback_json(project_id, media_id, headers=headers)
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status in (403, 404):
error = self._parse_json(e.cause.response.read().decode(), media_id, fatal=False)
message = traverse_obj(error, ('message', {clean_html}, filter))
code = traverse_obj(error, ('code', {clean_html}, filter))
error_id = traverse_obj(error, ('id', {int_or_none}))
if code == 'REQUEST_FAILED':
if error_id == 124:
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
elif error_id == 126:
raise ExtractorError('Access is denied (possibly due to invalid/missing API key)')
if code == 'MEDIA_NOT_FOUND':
raise ExtractorError(join_nonempty(code, message, delim=': '), expected=True)
if code or message:
raise ExtractorError(join_nonempty(code, error_id, message, delim=': '))
raise
streaks_id = response['id']
live_status = {
'clip': 'was_live',
'file': 'not_live',
'linear': 'is_live',
'live': 'is_live',
}.get(response.get('type'))
formats, subtitles = [], {}
drm_formats = False
sources = response['sources']
ssai = traverse_obj(sources, (..., 'ssai', {dict}, any))
for source in traverse_obj(sources, (
lambda _, v: url_or_none(v['src']),
)):View on GitHub (pinned to 81ecd58b13)
Solutions
- Re-run with yt_dlp -v to see the full message; the embedded code/id/message identifies the backend reason.
- If it looks like throttling, wait and retry with slower rate (--limit-rate, sleep_interval) or fewer parallel fragment downloads.
- Update yt-dlp to latest — new codes get mapped to friendlier errors over time.
- If the message indicates auth/entitlement, supply cookies (--cookies-from-browser) or view the page in a browser to see what access it demands.
Defensive patterns
Strategy: retry
Try / catch
from yt_dlp.utils import ExtractorError
for attempt in range(3):
try:
ydl.extract_info(url)
break
except ExtractorError as e:
if 'RATE_LIMIT' in str(e).upper():
time.sleep(30 * (attempt + 1))
continue
raise Prevention
- Rate-limit your own requests to the streaks API (sleep between items).
- Log the raw code/error_id passthrough — it identifies the true backend reason.
- Update yt-dlp so newly mapped codes surface friendlier errors.
When it happens
Trigger: The playback API returns an unmapped error code — rate limiting, expired token, bad request, project suspended — with 403/404 status and a JSON body containing 'code' and/or 'message'. Anything not matching the two special cases funnels here.
Common situations: Hammering the API from one IP triggers throttling codes; site adds a new error code the extractor has not mapped; entitlement/token codes when the media requires auth the caller did not send.
Related errors
- Unable to load data. Error code: {code}
- Unable to download video %s
- Iwara API said: {error or "nothing"}
- Iwara says: {errmsg}
- {} said: {} ({})
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/e32ac0b8b7a2bce7.
Report an issue: GitHub.