ytdl-org/youtube-dl · error · ExtractorError

Course %s is not free. You have to pay for it before you can

Error message

Course %s is not free. You have to pay for it before you can download. Use this URL to confirm purchase: %s

What it means

Raised by UdemyIE._enroll_course when the course page HTML exposes a /(payment|cart)/checkout/ link — the course is paid and not enrolled, so lecture content cannot be fetched. The error embeds the full checkout URL so the user can complete the purchase in a browser. Expected error.

Source

Thrown at youtube_dl/extractor/udemy.py:93

                r'ng-init=["\'].*\bcourse=({.+?})[;"\']',
                webpage, 'course', default='{}')),
            video_id, fatal=False) or {}
        course_id = course.get('id') or self._search_regex(
            [
                r'data-course-id=["\'](\d+)',
                r'"courseId"\s*:\s*(\d+)'
            ], webpage, 'course id')
        return course_id, course.get('title')

    def _enroll_course(self, base_url, webpage, course_id):
        def combine_url(base_url, url):
            return compat_urlparse.urljoin(base_url, url) if not url.startswith('http') else url

        checkout_url = unescapeHTML(self._search_regex(
            r'href=(["\'])(?P<url>(?:https?://(?:www\.)?udemy\.com)?/(?:payment|cart)/checkout/.+?)\1',
            webpage, 'checkout url', group='url', default=None))
        if checkout_url:
            raise ExtractorError(
                'Course %s is not free. You have to pay for it before you can download. '
                'Use this URL to confirm purchase: %s'
                % (course_id, combine_url(base_url, checkout_url)),
                expected=True)

        enroll_url = unescapeHTML(self._search_regex(
            r'href=(["\'])(?P<url>(?:https?://(?:www\.)?udemy\.com)?/course/subscribe/.+?)\1',
            webpage, 'enroll url', group='url', default=None))
        if enroll_url:
            webpage = self._download_webpage(
                combine_url(base_url, enroll_url),
                course_id, 'Enrolling in the course',
                headers={'Referer': base_url})
            if '>You have enrolled in' in webpage:
                self.to_screen('%s: Successfully enrolled in the course' % course_id)

    def _download_lecture(self, course_id, lecture_id):
        return self._download_json(

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Open the checkout URL from the error message in a browser, buy the course, then export cookies and rerun with --cookies.
  2. If a free coupon exists, enroll via the coupon link in the browser first so /course/subscribe/ is used, then pass those cookies.
  3. Log into Udemy in a browser and pass --username/--password or --cookies so _enroll_course sees your entitlement.
  4. Choose a genuinely free course/lecture.
Defensive patterns

Strategy: try-catch

Validate before calling

import requests
html = requests.get(lecture_url, cookies=udemy_cookies).text
if '/payment/checkout/' in html or '/cart/checkout/' in html:
    needs_purchase(course_id)

Type guard

def udemy_needs_checkout(html: str) -> bool:
    return '/payment/checkout/' in html or '/cart/checkout/' in html

Try / catch

try:
    ydl.extract_info(lecture_url)
except ExtractorError as e:
    if 'not free' in str(e):
        checkout = str(e).split('purchase: ')[-1].strip()
        record_checkout_url(course_id, checkout)
    else:
        raise

Prevention

When it happens

Trigger: Extracting an Udemy lecture while not enrolled: _extract_course_info loads the page, and the checkout-URL regex matches an href to /payment/checkout/ or /cart/checkout/, triggering the raise before the free /course/subscribe/ enroll path is tried.

Common situations: Paid Udemy courses without a logged-in enrolled account, logged-out users hitting paywalled lecture pages, free coupon not applied (must visit the coupon URL first to enroll).

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/fc2140fb98294a74. Report an issue: GitHub.