ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

Raised by the Funimation extractor when the showexperience API call returns 403 and the first entry of the body's 'errors' array is surfaced via its 'detail' or 'title' field. A 403 here means the session is not entitled to the stream: region lock, missing subscription, or an invalid/expired auth token in the Authorization header.

Source

Thrown at youtube_dl/extractor/funimation.py:125

        title = episode = title_data.get('title') or _search_kane('videoTitle') or self._og_search_title(webpage)
        series = _search_kane('showName')
        if series:
            title = '%s - %s' % (series, title)
        description = self._html_search_meta(['description', 'og:description'], webpage, fatal=True)

        try:
            headers = {}
            if self._TOKEN:
                headers['Authorization'] = 'Token %s' % self._TOKEN
            sources = self._download_json(
                'https://www.funimation.com/api/showexperience/%s/' % video_id,
                video_id, headers=headers, query={
                    'pinst_id': ''.join([random.choice(string.digits + string.ascii_letters) for _ in range(8)]),
                })['items']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                error = self._parse_json(e.cause.read(), video_id)['errors'][0]
                raise ExtractorError('%s said: %s' % (
                    self.IE_NAME, error.get('detail') or error.get('title')), expected=True)
            raise

        formats = []
        for source in sources:
            source_url = source.get('src')
            if not source_url:
                continue
            source_type = source.get('videoType') or determine_ext(source_url)
            if source_type == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    source_url, video_id, 'mp4',
                    m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'format_id': source_type,
                    'url': source_url,
                })

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Read the surfaced detail — it distinguishes geo-block from subscription errors
  2. Authenticate: --username/--password (Funimation login) or --cookies from a logged-in premium session
  3. For geo errors, use a US IP (Funimation streaming was region-segmented US/EU/UK with separate catalogs)
  4. Since Funimation merged into Crunchyroll, use the Crunchyroll extractor with those credentials if the title migrated

Example fix

# before
youtube_dl 'https://www.funimation.com/en/shows/some-show/episode-1/'
# ERROR: Funimation said: You are not subscribed to this series

# after
yt-dlp --cookies funi_cookies.txt 'https://www.funimation.com/en/shows/some-show/episode-1/'
Defensive patterns

Strategy: try-catch

Try / catch

try:
    ydl.extract_info(url)
except ExtractorError as e:
    if str(e).startswith('Funimation said:'):
        detail = str(e).split('said:', 1)[1]
        if 'region' in detail.lower() or 'geo' in detail.lower():
            rerun_with_us_proxy(url)
        else:
            rerun_with_credentials(url)

Prevention

When it happens

Trigger: _download_json of funimation.com/api/showexperience/<video_id>/?pinst_id=<random8> raises with an HTTPError 403; the body's errors[0] is parsed and raised as 'Funimation said: <detail|title>'. Occurs when self._TOKEN is absent (anonymous) or expired, the title is US-only while the requester is elsewhere, or the account lacks a premium subscription.

Common situations: Downloading without login from non-US IPs; tokens captured earlier expiring mid-session; free accounts hitting premium-only episodes; post-migration Funimation API behavior drift.

Related errors


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