ytdl-org/youtube-dl · error · ExtractorError

Gfycat said: %s

Error message

Gfycat said: %s

What it means

Raised by the Gfycat extractor when the API response from https://api.gfycat.com/v1/gfycats/<id> contains an 'error' key. The message is the API's own error string (e.g. 'Gfycat not found'). Marked expected=True, so it maps to a normal user-facing failure.

Source

Thrown at youtube_dl/extractor/gfycat.py:71

    }, {
        'url': 'https://gfycat.com/acceptablehappygoluckyharborporpoise-baseball',
        'only_matching': True
    }, {
        'url': 'https://thumbs.gfycat.com/acceptablehappygoluckyharborporpoise-size_restricted.gif',
        'only_matching': True
    }, {
        'url': 'https://giant.gfycat.com/acceptablehappygoluckyharborporpoise.mp4',
        'only_matching': True
    }]

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

        gfy = self._download_json(
            'https://api.gfycat.com/v1/gfycats/%s' % video_id,
            video_id, 'Downloading video info')
        if 'error' in gfy:
            raise ExtractorError('Gfycat said: ' + gfy['error'], expected=True)
        gfy = gfy['gfyItem']

        title = gfy.get('title') or gfy['gfyName']
        description = gfy.get('description')
        timestamp = int_or_none(gfy.get('createDate'))
        uploader = gfy.get('userName')
        view_count = int_or_none(gfy.get('views'))
        like_count = int_or_none(gfy.get('likes'))
        dislike_count = int_or_none(gfy.get('dislikes'))
        age_limit = 18 if gfy.get('nsfw') == '1' else 0

        width = int_or_none(gfy.get('width'))
        height = int_or_none(gfy.get('height'))
        fps = int_or_none(gfy.get('frameRate'))
        num_frames = int_or_none(gfy.get('numFrames'))

        duration = float_or_none(num_frames, fps) if num_frames and fps else None

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Confirm the gfycat still exists by opening the URL in a browser
  2. If deleted/removed, there is no fix — use an archived copy
  3. Try the direct CDN form (giant.gfycat.com/<name>.mp4) if the API item is gone
  4. Update youtube-dl/yt-dlp for any post-shutdown API changes
Defensive patterns

Strategy: try-catch

Validate before calling

import requests
r = requests.get(f'https://api.gfycat.com/v1/gfycats/{gfy_id}')
body = r.json()
if 'error' in body or 'gfyItem' not in body:
    print('Gfycat unavailable:', body.get('error', 'unknown'))

Type guard

def gfy_ok(body):
    return isinstance(body, dict) and 'error' not in body and isinstance(body.get('gfyItem'), dict)

Try / catch

try:
    info = ydl.extract_info(url)
except DownloadError as e:
    if 'Gfycat said' in str(e):
        skip_and_log(url, str(e))  # dead/private item — expected
    else:
        raise

Prevention

When it happens

Trigger: Requesting a gfycat.com URL whose ID does not exist, was deleted, or was made private — the API returns 200-style JSON body with {error: ...} rather than a hard HTTP error in some cases.

Common situations: Dead links (Gfycat shut down/red-flagged many items after the 2023 migration concerns), deleted or NSFW-gated items, typo'd IDs from URL rewriting, or regional blocks.

Related errors


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