ytdl-org/youtube-dl · error · ExtractorError

%s returned error: %s

Error message

%s returned error: %s

What it means

Raised by the Vidme extractor when the api.vid.me response JSON (normally fetched, or recovered from a 400 body) contains a non-empty top-level 'error' key. The API's message is passed through with the extractor name. expected=True — the API explicitly rejected the videoByUrl lookup.

Source

Thrown at youtube_dl/extractor/vidme.py:149

            'skip_download': True,
        },
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)

        try:
            response = self._download_json(
                'https://api.vid.me/videoByUrl/%s' % video_id, video_id)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
                response = self._parse_json(e.cause.read(), video_id)
            else:
                raise

        error = response.get('error')
        if error:
            raise ExtractorError(
                '%s returned error: %s' % (self.IE_NAME, error), expected=True)

        video = response['video']

        if video.get('state') == 'deleted':
            raise ExtractorError(
                'Vidme said: Sorry, this video has been deleted.',
                expected=True)

        if video.get('state') in ('user-disabled', 'suspended'):
            raise ExtractorError(
                'Vidme said: This video has been suspended either due to a copyright claim, '
                'or for violating the terms of use.',
                expected=True)

        formats = []
        for f in video.get('formats', []):
            format_url = url_or_none(f.get('uri'))

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Accept that Vidme is defunct — the API will not return video data for any id.
  2. Remove/skip vid.me URLs from batch downloads and link checkers.
  3. If you maintain a fork, consider deleting or disabling the extractor family since the service is dead.
  4. Do not attempt retries; the error is a definitive API rejection.
Defensive patterns

Strategy: validation

Validate before calling

import json, urllib.request
try:
    resp = json.load(urllib.request.urlopen('https://api.vid.me/videoByUrl/%s' % video_id))
except urllib.error.HTTPError as he:
    resp = json.loads(he.read().decode())
if resp.get('error'):
    skip(video_id, 'vidme api: %s' % resp['error'])

Try / catch

try:
    ydl.extract_info(url)
except ExtractorError as e:
    if e.expected and 'returned error' in str(e):
        archive_dead(url)  # service shut down; permanent
    raise

Prevention

When it happens

Trigger: Extracting a vid.me URL where the API answers {"error": ...} — nonexistent video slug, API version changes, or Vidme's shutdown responses. The same handler also covers the branch where a 400 response body is manually parsed.

Common situations: Vid.me shut down in late 2017, so virtually every vid.me URL now errors; archived playlists still contain vid.me links; the API domain no longer serves the videoByUrl contract.

Related errors


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