{"record":{"id":"9412f7946f996f61","repo":"ytdl-org/youtube-dl","slug":"parsed-json-message-from-http-400-401-403-resp","errorCode":null,"errorMessage":"parsed JSON ['message'] from HTTP 400/401/403 response body (dynamic)","messagePattern":"parsed JSON \\['message'\\] from HTTP 400/401/403 response body \\(dynamic\\)","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"youtube_dl/extractor/zype.py","lineNumber":51,"sourceCode":"    }\n\n    @staticmethod\n    def _extract_urls(webpage):\n        return [\n            mobj.group('url')\n            for mobj in re.finditer(\n                r'<script[^>]+\\bsrc=([\"\\'])(?P<url>(?:https?:)?%s.+?)\\1' % (ZypeIE._COMMON_RE % ZypeIE._ID_RE),\n                webpage)]\n\n    def _real_extract(self, url):\n        video_id = self._match_id(url)\n\n        try:\n            response = self._download_json(re.sub(\n                r'\\.(?:js|html)\\?', '.json?', url), video_id)['response']\n        except ExtractorError as e:\n            if isinstance(e.cause, compat_HTTPError) and e.cause.code in (400, 401, 403):\n                raise ExtractorError(self._parse_json(\n                    e.cause.read().decode(), video_id)['message'], expected=True)\n            raise\n\n        body = response['body']\n        video = response['video']\n        title = video['title']\n\n        if isinstance(body, dict):\n            formats = []\n            for output in body.get('outputs', []):\n                output_url = output.get('url')\n                if not output_url:\n                    continue\n                name = output.get('name')\n                if name == 'm3u8':\n                    formats = self._extract_m3u8_formats(\n                        output_url, video_id, 'mp4',\n                        'm3u8_native', m3u8_id='hls', fatal=False)","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/zype.py#L33-L69","documentation":"Raised by Zype extractors when the request to the .json variant of the video URL fails with HTTP 400, 401, or 403. The response body is parsed as JSON and its 'message' field is re-raised as an expected ExtractorError, surfacing the provider's own explanation (invalid video, unauthorized, forbidden).","triggerScenarios":"Downloading a Zype-hosted video (zype.com or an embedded player using the Zype JSON endpoint) where the API rejects the request: deleted/private video id, missing or expired access credentials, or a geo/IP block.","commonSituations":"Embedded Zype players on publisher sites whose videos require a subscription or login; expired embed URLs; publisher removed the video but the page still embeds the player.","solutions":["Open the page in a browser and confirm the video still plays without a login","If the video needs a subscription/login, use --username/--password or the site's supported auth flow","For geo blocks, retry from an allowed network (with --proxy if appropriate)","Update youtube-dl in case the Zype endpoint or auth scheme changed"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    ydl.download([url])\nexcept ExtractorError as e:\n    # e.msg is the provider's own message from the 400/401/403 body\n    if 'unauthorized' in str(e).lower():\n        attach_credentials_and_retry(url)\n    else:\n        raise","preventionTips":["Verify Zype embed URLs still play in a browser before batch processing","Supply subscription credentials where the publisher requires login","Log the dynamic message verbatim — it distinguishes deleted/forbidden/geo cases"],"tags":["zype","http-403","authorization","dynamic-message"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}