yt-dlp/yt-dlp · error · ExtractorError

{IE_NAME} returned error: {error}

Error message

{IE_NAME} returned error: {error}

What it means

Raised by UstreamIE._real_extract when the JSON from api.ustream.tv/videos/<id>.json contains a non-empty 'error' key; the API's own error text (e.g. 'not found') is appended to the message. The HTTP layer may still return 200 here, so this is the extractor's application-level check after a successful download. Expected=True.

Source

Thrown at yt_dlp/extractor/ustream.py:181

            video_id = m.group('id')
            desktop_url = 'http://www.ustream.tv/recorded/' + video_id
            return self.url_result(desktop_url, 'Ustream')
        if m.group('type') == 'embed':
            video_id = m.group('id')
            webpage = self._download_webpage(url, video_id)
            content_video_ids = self._parse_json(self._search_regex(
                r'ustream\.vars\.offAirContentVideoIds=([^;]+);', webpage,
                'content video IDs'), video_id)
            return self.playlist_result(
                (self.url_result('http://www.ustream.tv/recorded/' + u, 'Ustream') for u in content_video_ids),
                video_id)

        params = self._download_json(
            f'https://api.ustream.tv/videos/{video_id}.json', video_id)

        error = params.get('error')
        if error:
            raise ExtractorError(
                f'{self.IE_NAME} returned error: {error}', expected=True)

        video = params['video']

        title = video['title']
        filesize = float_or_none(video.get('file_size'))

        formats = [{
            'id': video_id,
            'url': video_url,
            'ext': format_id,
            'filesize': filesize,
        } for format_id, video_url in video['media_urls'].items() if video_url]

        if not formats:
            hls_streams = self._get_streams(url, video_id, app_id_ver=(11, 2))
            if hls_streams:
                # m3u8_native leads to intermittent ContentTooShortError

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Open the URL in a browser to confirm the recording still exists and is public
  2. If the channel is live, use the live URL form instead of /recorded/<id>
  3. Remove dead ids from automated queues and log them instead of retrying
  4. Update yt-dlp in case the API endpoint moved after platform changes
Defensive patterns

Strategy: try-catch

Type guard

def is_ustream_api_error(e: Exception) -> bool:
    return isinstance(e, ExtractorError) and 'Ustream returned error' in str(e)

Try / catch

try:
    ydl.extract_info(url, download=True)
except ExtractorError as e:
    if 'Ustream returned error' in str(e):
        archive_dead_recording(video_id)  # API-level rejection is terminal
    else:
        raise

Prevention

When it happens

Trigger: Extracting a ustream.tv/recorded/<id> (or IBM Video Streaming) URL whose API responds with an error object — deleted recordings, private videos, or invalid ids.

Common situations: Old recorded URLs after Ustream's IBM migration removed content; deleted or unlisted recordings; mistyped numeric ids; playlists referencing dead recordings.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/b6989d9d6bca49e5. Report an issue: GitHub.