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

  1. Clear the cached device credentials (youtube-dl --no-cache-dir or remove the 'cbcwatch' cache entry) so a fresh device is registered.
  2. Read the userMessage text — it usually states the real cause (e.g. 'not available in your region', 'token expired').
  3. Update to yt-dlp; CBC's platform changed substantially after this extractor was written.
  4. 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

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


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/e47a48211d7c8b64. Report an issue: GitHub.