{"record":{"id":"8c5b02520887872f","repo":"ytdl-org/youtube-dl","slug":"s-8c5b02","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":"ExtractorError","httpStatus":403,"severity":"error","filePath":"youtube_dl/extractor/sevenplus.py","lineNumber":61,"sourceCode":"    }]\n\n    def _real_extract(self, url):\n        path, episode_id = re.match(self._VALID_URL, url).groups()\n\n        try:\n            media = self._download_json(\n                'https://videoservice.swm.digital/playback', episode_id, query={\n                    'appId': '7plus',\n                    'deviceType': 'web',\n                    'platformType': 'web',\n                    'accountId': 5303576322001,\n                    'referenceId': 'ref:' + episode_id,\n                    'deliveryId': 'csai',\n                    'videoType': 'vod',\n                })['media']\n        except ExtractorError as e:\n            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:\n                raise ExtractorError(self._parse_json(\n                    e.cause.read().decode(), episode_id)[0]['error_code'], expected=True)\n            raise\n\n        for source in media.get('sources', {}):\n            src = source.get('src')\n            if not src:\n                continue\n            source['src'] = update_url_query(src, {'rule': ''})\n\n        info = self._parse_brightcove_metadata(media, episode_id)\n\n        content = self._download_json(\n            'https://component-cdn.swm.digital/content/' + path,\n            episode_id, headers={\n                'market-id': 4,\n            }, fatal=False) or {}\n        for item in content.get('items', {}):\n            if item.get('componentData', {}).get('componentType') == 'infoPanel':","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/sevenplus.py#L43-L79","documentation":"Raised by the SevenplusIE extractor when the 7plus Brightcove playback API (videoservice.swm.digital) answers an HTTP 403. The body of the 403 response is parsed as JSON and the first entry's 'error_code' field is surfaced as the error message (e.g. GEO_RESTRICTED or content-unavailable codes). It is marked expected=True, so youtube-dl reports it as a known, user-facing failure rather than a bug.","triggerScenarios":"Calling _download_json on https://videoservice.swm.digital/playback with the shown query (appId=7plus, deliveryId=csai, referenceId=ref:<episode_id>) and receiving an HTTP 403 whose JSON body contains an 'error_code'. Typical causes: geo-blocked IP (7plus is Australia-only), expired/rotated episode referenceId, or the CDN rejecting the 'rule=' query rewrite.","commonSituations":"Running youtube-dl outside Australia for a 7plus URL; using a stale episode URL after SWM rotated content; a VPN endpoint blacklisted by the Brightcove/SWM edge; upstream schema change where the 403 body is HTML instead of JSON (then _parse_json itself fails).","solutions":["If outside Australia, route through an Australian VPN/proxy or accept the geo block.","Re-fetch the episode page to get a fresh episode_id/referenceId and retry with the new URL.","Inspect e.cause.read() manually (curl the playback endpoint) to confirm which error_code the API returns before assuming geo-blocking.","If the 403 body is no longer JSON, update the extractor to tolerate non-JSON error bodies."],"exampleFix":"// before\nraise ExtractorError(self._parse_json(\n    e.cause.read().decode(), episode_id)[0]['error_code'], expected=True)\n\n// after (tolerate non-JSON bodies)\ntry:\n    error_code = self._parse_json(\n        e.cause.read().decode(), episode_id)[0]['error_code']\nexcept ExtractorError:\n    error_code = '7plus returned HTTP 403'\nraise ExtractorError(error_code, expected=True)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    ie.extract(url)\nexcept ExtractorError as e:\n    if e.expected and 'GEO' in str(e).upper():\n        # geo block: route via AU proxy or skip\n        handle_geo_block(e)\n    else:\n        raise","preventionTips":["Check content availability region (7plus is Australia-only) before batch jobs.","Resolve fresh episode URLs at extract time instead of caching ids.","Batch drivers should catch expected ExtractorError and continue, not abort the queue."],"tags":["geo-restricted","http-403","brightcove","sevenplus","extractor"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}