{"record":{"id":"e6050e40fbd160c4","repo":"yt-dlp/yt-dlp","slug":"invalid-username-password","errorCode":null,"errorMessage":"Invalid username/password","messagePattern":"Invalid username/password","errorType":"exception","errorClass":"ExtractorError","httpStatus":401,"severity":"error","filePath":"yt_dlp/extractor/gamedevtv.py","lineNumber":74,"sourceCode":"            'alt_title': '1_CC_MVX MagicaVoxel Community Course Introduction.mp4',\n            'thumbnail': 'https://vz-23691c65-6fa.b-cdn.net/df04f4d8-68a4-4756-a71b-9ca9446c3a01/thumbnail.jpg',\n        },\n    }]\n    _API_HEADERS = {}\n\n    def _perform_login(self, username, password):\n        try:\n            response = self._download_json(\n                'https://api.gamedev.tv/api/students/login', None, 'Logging in',\n                headers={'Content-Type': 'application/json'},\n                data=json.dumps({\n                    'email': username,\n                    'password': password,\n                    'cart_items': [],\n                }).encode())\n        except ExtractorError as e:\n            if isinstance(e.cause, HTTPError) and e.cause.status == 401:\n                raise ExtractorError('Invalid username/password', expected=True)\n            raise\n\n        self._API_HEADERS['Authorization'] = f'{response[\"token_type\"]} {response[\"access_token\"]}'\n\n    def _real_initialize(self):\n        if not self._API_HEADERS.get('Authorization'):\n            self.raise_login_required(\n                'This content is only available with purchase', method='password')\n\n    def _entries(self, data, course_id, course_info, selected_lecture):\n        for section in traverse_obj(data, ('sections', ..., {dict})):\n            section_info = traverse_obj(section, {\n                'season_id': ('id', {str_or_none}),\n                'season': ('title', {str}),\n                'season_number': ('order', {int_or_none}),\n            })\n            for lecture in traverse_obj(section, ('lectures', lambda _, v: url_or_none(v['video']['playListUrl']))):\n                if selected_lecture and str(lecture.get('id')) != selected_lecture:","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/yt-dlp/yt-dlp/blob/81ecd58b1394793e6da9998cc19fdb45657f1685/yt_dlp/extractor/gamedevtv.py#L56-L92","documentation":"Raised by GameDevTVDLCourseIE._perform_login. The extractor POSTs email/password as JSON to https://api.gamedev.tv/api/students/login; an HTTP 401 response is translated to this expected 'Invalid username/password' error. On success, response['token_type'] + response['access_token'] become the Authorization header for all course API calls; without a login, _real_initialize raises login-required (content needs purchase).","triggerScenarios":"Wrong email or password supplied to --username/--password; .netrc with stale entries; account that exists on the site but has no active course purchase (still logs in, fails later); API base URL changed by the site so the login endpoint 401s.","commonSituations":"Credential typos in automation; password rotations not reflected in .netrc; confusion between gamedev.tv site login and the api.gamedev.tv student login.","solutions":["Confirm the email/password by logging in at gamedev.tv in a browser","Re-run with the corrected --username/--password (or updated .netrc)","If credentials are right but courses fail, check the account actually owns the course","Update yt-dlp if the login endpoint moved"],"exampleFix":"# before\nyt-dlp --username me@mail.com --password 'old' 'https://www.gamedev.tv/courses/x'\n# after: corrected credentials\nyt-dlp --username me@mail.com --password 'new' 'https://www.gamedev.tv/courses/x'","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"# If you call api.gamedev.tv yourself, guard the login response first\ndef token_of(response: dict) -> str | None:\n    if not isinstance(response, dict):\n        return None\n    tok, typ = response.get('access_token'), response.get('token_type')\n    return f'{typ} {tok}' if tok and typ else None","tryCatchPattern":"from yt_dlp import YoutubeDL\nfrom yt_dlp.utils import DownloadError\n\ntry:\n    with YoutubeDL({'username': USER, 'password': PASS}) as ydl:\n        ydl.download([course_url])\nexcept DownloadError as e:\n    if 'Invalid username/password' in str(e):\n        fail_fast('fix credentials in .netrc / CLI arguments')  # do NOT retry-loop a 401\n    else:\n        raise","preventionTips":["Confirm the login works at gamedev.tv before using it in scripts","Never retry-loop a 401 - fix the credentials instead of hammering the endpoint","Keep .netrc permissions tight and update it on every password rotation"],"tags":["gamedevtv","login","invalid-credentials","http-401","oauth-token"],"backgroundTag":"invalid-credentials","analyzedSha":"81ecd58b1394793e6da9998cc19fdb45657f1685","analyzedAt":"2026-08-22T12:21:25.439Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}