{"record":{"id":"af81827a1aca8491","repo":"yt-dlp/yt-dlp","slug":"failure","errorCode":null,"errorMessage":"failure","messagePattern":"failure","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"yt_dlp/extractor/piksel.py","lineNumber":78,"sourceCode":"                'upload_date': '20170204',\n                'thumbnail': 'https://thumbs.piksel.tech/thumbs/aid/t1495569155/3279887.jpg?w=640&h=360',\n            },\n        },\n        {\n            # https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2019240/\n            'url': 'http://player.piksel.com/v/refid/nhkworld/prefid/nw_vod_v_en_2019_240_20190823233000_02_1566873477',\n            'only_matching': True,\n        },\n    ]\n\n    def _call_api(self, app_token, resource, display_id, query, host='https://player.piksel.tech', fatal=True):\n        url = urljoin(host, f'/ws/ws_{resource}/api/{app_token}/mode/json/apiv/5')\n        response = traverse_obj(\n            self._download_json(url, display_id, query=query, fatal=fatal), ('response', {dict})) or {}\n        failure = traverse_obj(response, ('failure', 'reason')) if response else 'Empty response from API'\n        if failure:\n            if fatal:\n                raise ExtractorError(failure, expected=True)\n            self.report_warning(failure)\n        return response\n\n    def _real_extract(self, url):\n        ref_id, display_id = self._match_valid_url(url).groups()\n        webpage = self._download_webpage(url, display_id)\n        app_token = self._search_regex([\n            r'clientAPI\\s*:\\s*\"([^\"]+)\"',\n            r'data-de-api-key\\s*=\\s*\"([^\"]+)\"',\n        ], webpage, 'app token')\n        query = {'refid': ref_id, 'prefid': display_id} if ref_id else {'v': display_id}\n        program = self._call_api(\n            app_token, 'program', display_id, query, url)['WsProgramResponse']['program']\n        video_id = program['uuid']\n        video_data = program['asset']\n        title = video_data['title']\n        asset_type = dict_get(video_data, ['assetType', 'asset_type'])\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/yt-dlp/yt-dlp/blob/81ecd58b1394793e6da9998cc19fdb45657f1685/yt_dlp/extractor/piksel.py#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Verify the refid/prefid in the URL are still valid by opening the player page in a browser.","Update yt-dlp; the app_token regexes (clientAPI / data-de-api-key) have been adjusted before when Piksel pages changed.","If the API host changed, check the extractor source for the current default host of _call_api."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"from yt_dlp.utils import ExtractorError\n\ndef is_piksel_api_failure(e: BaseException) -> bool:\n    \"\"\"True when the Piksel API returned a failure envelope or empty response.\"\"\"\n    return isinstance(e, ExtractorError) and (\n        'Empty response from API' in str(e) or e.expected)","tryCatchPattern":"from yt_dlp.utils import ExtractorError\n\ntry:\n    info = ydl.extract_info(url)\nexcept ExtractorError as e:\n    # e carries the API's own failure.reason text; log it verbatim and give up on this asset\n    logger.warning('piksel api refused %s: %s', url, e)\n    if not e.expected:\n        raise","preventionTips":["Treat the error text as authoritative API output — it names the actual failure reason.","Check that Piksel player URLs still resolve in a browser before batch-downloading many of them.","Keep yt-dlp updated; app_token scraping and the default API host have both changed historically."],"tags":["piksel","nhkworld","api-response","extractor","yt-dlp"],"backgroundTag":"api-error-response","analyzedSha":"81ecd58b1394793e6da9998cc19fdb45657f1685","analyzedAt":"2026-08-22T12:21:25.439Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}