yt-dlp/yt-dlp · error · ExtractorError

failure

Error message

failure

What it means

PikselIE._call_api (yt_dlp/extractor/piksel.py:78) wraps the Piksel player API at https://player.piksel.tech/ws/ws_<resource>/api/<app_token>/mode/json/apiv/5. It inspects the JSON envelope: if response contains a failure.reason, that reason string is re-raised verbatim as an expected ExtractorError; if the response object is empty, the message is 'Empty response from API'. With fatal=False the same condition is only a warning and extraction continues.

Source

Thrown at yt_dlp/extractor/piksel.py:78

                'upload_date': '20170204',
                'thumbnail': 'https://thumbs.piksel.tech/thumbs/aid/t1495569155/3279887.jpg?w=640&h=360',
            },
        },
        {
            # https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2019240/
            'url': 'http://player.piksel.com/v/refid/nhkworld/prefid/nw_vod_v_en_2019_240_20190823233000_02_1566873477',
            'only_matching': True,
        },
    ]

    def _call_api(self, app_token, resource, display_id, query, host='https://player.piksel.tech', fatal=True):
        url = urljoin(host, f'/ws/ws_{resource}/api/{app_token}/mode/json/apiv/5')
        response = traverse_obj(
            self._download_json(url, display_id, query=query, fatal=fatal), ('response', {dict})) or {}
        failure = traverse_obj(response, ('failure', 'reason')) if response else 'Empty response from API'
        if failure:
            if fatal:
                raise ExtractorError(failure, expected=True)
            self.report_warning(failure)
        return response

    def _real_extract(self, url):
        ref_id, display_id = self._match_valid_url(url).groups()
        webpage = self._download_webpage(url, display_id)
        app_token = self._search_regex([
            r'clientAPI\s*:\s*"([^"]+)"',
            r'data-de-api-key\s*=\s*"([^"]+)"',
        ], webpage, 'app token')
        query = {'refid': ref_id, 'prefid': display_id} if ref_id else {'v': display_id}
        program = self._call_api(
            app_token, 'program', display_id, query, url)['WsProgramResponse']['program']
        video_id = program['uuid']
        video_data = program['asset']
        title = video_data['title']
        asset_type = dict_get(video_data, ['assetType', 'asset_type'])

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Read the actual failure reason in the message — it comes straight from the Piksel API and names the real problem (expired asset, bad ref, auth).
  2. Verify the refid/prefid in the URL are still valid by opening the player page in a browser.
  3. Update yt-dlp; the app_token regexes (clientAPI / data-de-api-key) have been adjusted before when Piksel pages changed.
  4. If the API host changed, check the extractor source for the current default host of _call_api.
Defensive patterns

Strategy: try-catch

Type guard

from yt_dlp.utils import ExtractorError

def is_piksel_api_failure(e: BaseException) -> bool:
    """True when the Piksel API returned a failure envelope or empty response."""
    return isinstance(e, ExtractorError) and (
        'Empty response from API' in str(e) or e.expected)

Try / catch

from yt_dlp.utils import ExtractorError

try:
    info = ydl.extract_info(url)
except ExtractorError as e:
    # e carries the API's own failure.reason text; log it verbatim and give up on this asset
    logger.warning('piksel api refused %s: %s', url, e)
    if not e.expected:
        raise

Prevention

When it happens

Trigger: Any Piksel-hosted player API call (used for NHK World VOD and other Piksel customers) where the API returns response.failure.reason — e.g. an expired or removed refid/prefid asset, a wrong app_token scraped from the page, or the API answering with an empty response object. The exact API reason text becomes the error message.

Common situations: Old NHK World VOD links (player.piksel.com/v/refid/nhkworld/prefid/...) whose assets expired; the data-de-api-key / clientAPI markup changed after a page redesign so the app_token is wrong; the API host moving between player.piksel.com and player.piksel.tech.

Related errors


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