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
- Confirm the gfycat still exists by opening the URL in a browser
- If deleted/removed, there is no fix — use an archived copy
- Try the direct CDN form (giant.gfycat.com/<name>.mp4) if the API item is gone
- 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
- Treat Gfycat links as perishable; verify before batch jobs
- Keep the giant.gfycat.com/<name>.mp4 direct URL as a fallback
- Log the API's message — it says whether it's deleted vs private
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
- Did not get a media key
- %s returned error: %s
- %s said: There's something wrong in the video.
- Bigo says: %s (code %s)
- %s said: %s
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/1428219eaddfa3c8.
Report an issue: GitHub.