{"record":{"id":"9040214ff730a58c","repo":"ytdl-org/youtube-dl","slug":"message","errorCode":null,"errorMessage":"message","messagePattern":"message","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"youtube_dl/extractor/brightcove.py","lineNumber":656,"sourceCode":"        for _ in range(2):\n            if not policy_key:\n                policy_key = extract_policy_key()\n                policy_key_extracted = True\n            headers['Accept'] = 'application/json;pk=%s' % policy_key\n            try:\n                json_data = self._download_json(api_url, video_id, headers=headers)\n                break\n            except ExtractorError as e:\n                if isinstance(e.cause, compat_HTTPError) and e.cause.code in (401, 403):\n                    json_data = self._parse_json(e.cause.read().decode(), video_id)[0]\n                    message = json_data.get('message') or json_data['error_code']\n                    if json_data.get('error_subcode') == 'CLIENT_GEO':\n                        self.raise_geo_restricted(msg=message)\n                    elif json_data.get('error_code') == 'INVALID_POLICY_KEY' and not policy_key_extracted:\n                        policy_key = None\n                        store_pk(None)\n                        continue\n                    raise ExtractorError(message, expected=True)\n                raise\n\n        errors = json_data.get('errors')\n        if errors and errors[0].get('error_subcode') == 'TVE_AUTH':\n            custom_fields = json_data['custom_fields']\n            tve_token = self._extract_mvpd_auth(\n                smuggled_data['source_url'], video_id,\n                custom_fields['bcadobepassrequestorid'],\n                custom_fields['bcadobepassresourceid'])\n            json_data = self._download_json(\n                api_url, video_id, headers={\n                    'Accept': 'application/json;pk=%s' % policy_key\n                }, query={\n                    'tveToken': tve_token,\n                })\n\n        if content_type == 'playlist':\n            return self.playlist_result(","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/brightcove.py#L638-L674","documentation":"Raised in BrightcoveNewIE's policy-key retry loop when the playback API answers 401/403. The body is parsed as JSON and the message is json_data.get('message') or json_data['error_code']. Before re-raising it handles two special cases: error_subcode CLIENT_GEO raises a geo-restricted error, and error_code INVALID_POLICY_KEY with a freshly extracted policy key clears the cached key and retries once.","triggerScenarios":"HTTP 401/403 from the playback API where the JSON error is neither CLIENT_GEO nor a first-time INVALID_POLICY_KEY — e.g. an API key/typo'd policy key, an expired token, or access-control rejection. CLIENT_GEO produces a GeoRestrictedError instead; persistent INVALID_POLICY_KEY after one retry falls through to this raise.","commonSituations":"Stale cached policy key in youtube-dl's cache after the site rotated keys; extracting from a region the account blocks; running an old youtube-dl against a changed Brightcove API.","solutions":["Clear the cached policy key: youtube-dl --no-cache-dir (or remove the cache directory) and retry so a fresh key is scraped from the player page.","If the error is geo related (CLIENT_GEO shows as geo-restricted), use --proxy or run from an allowed country.","Update to yt-dlp for current policy-key extraction logic.","Confirm the account/player id in the URL is correct — wrong ids commonly produce 401/403 JSON errors."],"exampleFix":"# before\nydl.extract(url)  # 403 INVALID_POLICY_KEY from stale cache\n\n# after\nfrom youtube_dl import YoutubeDL\nwith YoutubeDL({'no-cache-dir': True}) as ydl:\n    ydl.extract(url)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from youtube_dl.utils import ExtractorError, GeoRestrictedError\ntry:\n    info = ydl_no_cache.extract_info(url, download=False)\nexcept GeoRestrictedError:\n    info = ydl_with_proxy.extract_info(url, download=False)\nexcept ExtractorError as e:\n    if 'POLICY_KEY' in str(e):  # retry once with a cold cache\n        info = YoutubeDL({'no_cache-dir': True}).extract_info(url, download=False)\n    else:\n        raise","preventionTips":["Run with --no-cache-dir when Brightcove sites rotate policy keys.","Distinguish GeoRestrictedError from plain ExtractorError in callers.","Verify account/player id correctness before extraction."],"tags":["brightcove","policy-key","http-403","cache"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}