{"record":{"id":"dbdfc41682edb527","repo":"yt-dlp/yt-dlp","slug":"msg-detail","errorCode":null,"errorMessage":"{msg['detail']}","messagePattern":"\\{msg\\['detail'\\]\\}","errorType":"exception","errorClass":"ExtractorError","httpStatus":403,"severity":"error","filePath":"yt_dlp/extractor/viidea.py","lineNumber":136,"sourceCode":"\n        cfg = self._parse_json(self._search_regex(\n            [r'cfg\\s*:\\s*({.+?})\\s*,\\s*[\\da-zA-Z_]+\\s*:\\s*\\(?\\s*function',\n             r'cfg\\s*:\\s*({[^}]+})'],\n            webpage, 'cfg'), lecture_slug, js_to_json)\n\n        lecture_id = str(cfg['obj_id'])\n\n        base_url = self._proto_relative_url(cfg['livepipe'], 'http:')\n\n        try:\n            lecture_data = self._download_json(\n                f'{base_url}/site/api/lecture/{lecture_id}?format=json',\n                lecture_id)['lecture'][0]\n        except ExtractorError as e:\n            if isinstance(e.cause, HTTPError) and e.cause.status == 403:\n                msg = self._parse_json(\n                    e.cause.response.read().decode('utf-8'), lecture_id)\n                raise ExtractorError(msg['detail'], expected=True)\n            raise\n\n        lecture_info = {\n            'id': lecture_id,\n            'display_id': lecture_slug,\n            'title': lecture_data['title'],\n            'timestamp': parse_iso8601(lecture_data.get('time')),\n            'description': lecture_data.get('description_wiki'),\n            'thumbnail': lecture_data.get('thumb'),\n        }\n\n        playlist_entries = []\n        lecture_type = lecture_data.get('type')\n        parts = [str(video) for video in cfg.get('videos', [])]\n        if parts:\n            multipart = len(parts) > 1\n\n            def extract_part(part_id):","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/yt-dlp/yt-dlp/blob/81ecd58b1394793e6da9998cc19fdb45657f1685/yt_dlp/extractor/viidea.py#L118-L154","documentation":"Viidea (lecture-capture platform) extractor surfaces the API's own error text: when the lecture metadata request ({livepipe}/site/api/lecture/{id}?format=json) returns HTTP 403, the JSON response's 'detail' field is re-raised as the error message with expected=True. Whatever the server says — password required, lecture unpublished, permissions — becomes the user-visible message.","triggerScenarios":"The lecture API returns 403 with a JSON body containing a 'detail' key — e.g. the lecture is not yet published, is instructor-only, or the institution disabled anonymous access to the recording.","commonSituations":"Students downloading lectures before they are made public, institutional portals that require an authenticated session, or lectures whose availability window has closed.","solutions":["Read the detail text — it states the server's reason (locked, unpublished, unauthorized).","If the portal requires login, authenticate in a browser, export cookies, and pass --cookies.","Confirm the lecture is playable in a browser while logged in; if not, it cannot be downloaded either.","Update yt-dlp in case the API contract changed."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    info = ydl.extract_info(url, download=True)\nexcept DownloadError as e:\n    if '[viidea]' in str(e):\n        # e.message is the server's own 'detail' text (locked/unpublished/unauthorized)\n        log_warning(f'lecture API refused: {e}')\n    else:\n        raise","preventionTips":["Check the lecture plays in a browser (logged in) before batch downloading.","Export portal cookies when the institution requires a session.","Schedule downloads after lectures are published, not before."],"tags":["viidea","http-403","api-error","lecture-capture"],"backgroundTag":"http-403-forbidden","analyzedSha":"81ecd58b1394793e6da9998cc19fdb45657f1685","analyzedAt":"2026-08-22T12:21:25.439Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}