ytdl-org/youtube-dl · error · ExtractorError
%s said: %s
Error message
%s said: %s
What it means
Raised by CBC's watch (Clearleap-based) device API layer when the returned XML contains a <userMessage> or <systemMessage> element. The message format is '<IE_NAME> said: <message>'. These elements are how the CBC/Clearleap backend reports application-level failures (expired tokens, unavailable content, entitlement problems) inside an HTTP-200 response.
Source
Thrown at youtube_dl/extractor/cbc.py:261
return resp['signature']
def _call_api(self, path, video_id):
url = path if path.startswith('http') else self._API_BASE_URL + path
for _ in range(2):
try:
result = self._download_xml(url, video_id, headers={
'X-Clearleap-DeviceId': self._device_id,
'X-Clearleap-DeviceToken': self._device_token,
})
except ExtractorError as e:
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
# Device token has expired, re-acquiring device token
self._register_device()
continue
raise
error_message = xpath_text(result, 'userMessage') or xpath_text(result, 'systemMessage')
if error_message:
raise ExtractorError('%s said: %s' % (self.IE_NAME, error_message))
return result
def _real_initialize(self):
if self._valid_device_token():
return
device = self._downloader.cache.load(
'cbcwatch', self._cache_device_key()) or {}
self._device_id, self._device_token = device.get('id'), device.get('token')
if self._valid_device_token():
return
self._register_device()
def _valid_device_token(self):
return self._device_id and self._device_token
def _cache_device_key(self):
email, _ = self._get_login_info()
return '%s_device' % hashlib.sha256(email.encode()).hexdigest() if email else 'device'View on GitHub (pinned to 956b8c5855)
Solutions
- Clear the cached device credentials (youtube-dl --no-cache-dir or remove the 'cbcwatch' cache entry) so a fresh device is registered.
- Read the userMessage text — it usually states the real cause (e.g. 'not available in your region', 'token expired').
- Update to yt-dlp; CBC's platform changed substantially after this extractor was written.
- If entitlement-gated, ensure you are entitled (sign in / region) before extracting.
Example fix
# before
ydl.extract(cbc_watch_url) # 'CBC said: <backend message>'
# after
from youtube_dl import YoutubeDL
with YoutubeDL({'no-cache-dir': True}) as ydl: # force device re-registration
ydl.extract(cbc_watch_url) Defensive patterns
Strategy: retry
Try / catch
from youtube_dl.utils import ExtractorError
for attempt in range(2):
try:
ydl.extract_info(url)
break
except ExtractorError as e:
if 'said:' in str(e) and attempt == 0:
# device token may be stale -> clear cache and re-register once
ydl = YoutubeDL({'no-cache-dir': True})
continue
raise Prevention
- Cache CBC device tokens yourself but invalidate aggressively.
- Parse the 'userMessage' text to decide retry vs permanent failure.
- Treat CBC platform changes as likely — prefer maintained forks.
When it happens
Trigger: Any successful HTTP response from the CBC watch XML API whose body carries userMessage/systemMessage — e.g. an invalid or revoked device token that still passes the 401 check, content not entitled to the account/region, or a video id the backend rejects. Note a real 401 is retried transparently after re-registering the device.
Common situations: Device registration cache stale or invalidated by CBC; content geo- or entitlement-restricted; API changes on CBC's side breaking the request so the backend replies with an error envelope.
Related errors
- Missing "id" field in extractor result
- Missing "title" field in extractor result
- No video formats found!
- Failed to get the video URL
- Invalid rendition field.
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/e47a48211d7c8b64.
Report an issue: GitHub.