yt-dlp/yt-dlp · error · ExtractorError
No chapters found for course {course_id}
Error message
No chapters found for course {course_id} What it means
SafariCourseIE turns an O'Reilly Learning (Safari) course into a playlist by downloading {self._API_BASE}/book/{course_id}/?override_format=... (API base https://learning.oreilly.com/api/v1) and requires a 'chapters' key in that JSON. When the API responds but the key is absent, it raises this expected ExtractorError rather than returning an empty playlist. In practice the id is usually not a course (single chapter/lesson ids, removed or non-entitled courses) or the API response shape changed.
Source
Thrown at yt_dlp/extractor/safari.py:246
}, {
'url': 'https://www.oreilly.com/library/view/hadoop-fundamentals-livelessons/9780133392838/',
'only_matching': True,
}]
@classmethod
def suitable(cls, url):
return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)
else super().suitable(url))
def _real_extract(self, url):
course_id = self._match_id(url)
course_json = self._download_json(
f'{self._API_BASE}/book/{course_id}/?override_format={self._API_FORMAT}',
course_id, 'Downloading course JSON')
if 'chapters' not in course_json:
raise ExtractorError(
f'No chapters found for course {course_id}', expected=True)
entries = [
self.url_result(chapter, SafariApiIE.ie_key())
for chapter in course_json['chapters']]
course_title = course_json['title']
return self.playlist_result(entries, course_id, course_title)
View on GitHub (pinned to 81ecd58b13)
Solutions
- Confirm the URL is a course page (shows a chapter list on learning.oreilly.com), not a single lesson link
- Fetch https://learning.oreilly.com/api/v1/book/{course_id}/ with the same session cookies and check whether 'chapters' exists
- If the id is a chapter, extract the parent course URL instead and rerun
- Update yt-dlp (yt-dlp -U) in case the extractor/API contract changed, and file an upstream issue if the JSON genuinely lacks chapters
Example fix
// before
if 'chapters' not in course_json:
raise ExtractorError(f'No chapters found for course {course_id}', expected=True)
// after: keep the guard but say why the chapters list is missing
if not course_json.get('chapters'):
raise ExtractorError(
f'No chapters found for course {course_id} (keys: {", ".join(course_json)}). '
'Verify the id is a course and the account has access.', expected=True) Defensive patterns
Strategy: try-catch
Type guard
def course_json_has_chapters(course_json) -> bool:
return (
isinstance(course_json, dict)
and isinstance(course_json.get('chapters'), list)
and len(course_json['chapters']) > 0
) Try / catch
from yt_dlp import YoutubeDL
from yt_dlp.utils import ExtractorError
with YoutubeDL({'quiet': True}) as ydl:
try:
playlist = ydl.extract_info(url, download=False)
except ExtractorError as e:
if e.expected and 'No chapters found for course' in str(e):
log.warning('skipping %s: id is not exposing chapters (not a course / no entitlement)', url)
else:
raise Prevention
- Queue course landing-page URLs, not links to individual chapters
- Validate course ids against the learning.oreilly.com API before batch processing
- Keep yt-dlp updated so API-contract fixes are picked up
When it happens
Trigger: Calling the extractor on a Safari course URL whose /book/{course_id}/ API payload has no 'chapters' key: chapter or lesson ids used as course ids, courses the logged-in account cannot see, or API schema drift after a yt-dlp release.
Common situations: Deep-linking a single chapter and treating it as a course; expired subscription cookies producing limited payloads; typo'd course id; yt-dlp version predating an API change on learning.oreilly.com.
Related errors
- No videoUrl and no information about errors
- Wrong regex for subtitlelangs: {e.pattern}
- file:// URLs are disabled by default in yt-dlp for security
- To use an HTTPS proxy for this request, one of the following
- This request requires WebSocket support. Ensure one of the f
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/c46775a33507d716.
Report an issue: GitHub.