yt-dlp/yt-dlp · error · ExtractorError

Invalid username/password

Error message

Invalid username/password

What it means

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).

Source

Thrown at yt_dlp/extractor/gamedevtv.py:74

            'alt_title': '1_CC_MVX MagicaVoxel Community Course Introduction.mp4',
            'thumbnail': 'https://vz-23691c65-6fa.b-cdn.net/df04f4d8-68a4-4756-a71b-9ca9446c3a01/thumbnail.jpg',
        },
    }]
    _API_HEADERS = {}

    def _perform_login(self, username, password):
        try:
            response = self._download_json(
                'https://api.gamedev.tv/api/students/login', None, 'Logging in',
                headers={'Content-Type': 'application/json'},
                data=json.dumps({
                    'email': username,
                    'password': password,
                    'cart_items': [],
                }).encode())
        except ExtractorError as e:
            if isinstance(e.cause, HTTPError) and e.cause.status == 401:
                raise ExtractorError('Invalid username/password', expected=True)
            raise

        self._API_HEADERS['Authorization'] = f'{response["token_type"]} {response["access_token"]}'

    def _real_initialize(self):
        if not self._API_HEADERS.get('Authorization'):
            self.raise_login_required(
                'This content is only available with purchase', method='password')

    def _entries(self, data, course_id, course_info, selected_lecture):
        for section in traverse_obj(data, ('sections', ..., {dict})):
            section_info = traverse_obj(section, {
                'season_id': ('id', {str_or_none}),
                'season': ('title', {str}),
                'season_number': ('order', {int_or_none}),
            })
            for lecture in traverse_obj(section, ('lectures', lambda _, v: url_or_none(v['video']['playListUrl']))):
                if selected_lecture and str(lecture.get('id')) != selected_lecture:

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Confirm the email/password by logging in at gamedev.tv in a browser
  2. Re-run with the corrected --username/--password (or updated .netrc)
  3. If credentials are right but courses fail, check the account actually owns the course
  4. Update yt-dlp if the login endpoint moved

Example fix

# before
yt-dlp --username me@mail.com --password 'old' 'https://www.gamedev.tv/courses/x'
# after: corrected credentials
yt-dlp --username me@mail.com --password 'new' 'https://www.gamedev.tv/courses/x'
Defensive patterns

Strategy: try-catch

Type guard

# If you call api.gamedev.tv yourself, guard the login response first
def token_of(response: dict) -> str | None:
    if not isinstance(response, dict):
        return None
    tok, typ = response.get('access_token'), response.get('token_type')
    return f'{typ} {tok}' if tok and typ else None

Try / catch

from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadError

try:
    with YoutubeDL({'username': USER, 'password': PASS}) as ydl:
        ydl.download([course_url])
except DownloadError as e:
    if 'Invalid username/password' in str(e):
        fail_fast('fix credentials in .netrc / CLI arguments')  # do NOT retry-loop a 401
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: Credential typos in automation; password rotations not reflected in .netrc; confusion between gamedev.tv site login and the api.gamedev.tv student login.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/e6050e40fbd160c4. Report an issue: GitHub.