{"record":{"id":"1428219eaddfa3c8","repo":"ytdl-org/youtube-dl","slug":"gfycat-said-s","errorCode":null,"errorMessage":"Gfycat said: %s","messagePattern":"Gfycat said: (.+?)","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"youtube_dl/extractor/gfycat.py","lineNumber":71,"sourceCode":"    }, {\n        'url': 'https://gfycat.com/acceptablehappygoluckyharborporpoise-baseball',\n        'only_matching': True\n    }, {\n        'url': 'https://thumbs.gfycat.com/acceptablehappygoluckyharborporpoise-size_restricted.gif',\n        'only_matching': True\n    }, {\n        'url': 'https://giant.gfycat.com/acceptablehappygoluckyharborporpoise.mp4',\n        'only_matching': True\n    }]\n\n    def _real_extract(self, url):\n        video_id = self._match_id(url)\n\n        gfy = self._download_json(\n            'https://api.gfycat.com/v1/gfycats/%s' % video_id,\n            video_id, 'Downloading video info')\n        if 'error' in gfy:\n            raise ExtractorError('Gfycat said: ' + gfy['error'], expected=True)\n        gfy = gfy['gfyItem']\n\n        title = gfy.get('title') or gfy['gfyName']\n        description = gfy.get('description')\n        timestamp = int_or_none(gfy.get('createDate'))\n        uploader = gfy.get('userName')\n        view_count = int_or_none(gfy.get('views'))\n        like_count = int_or_none(gfy.get('likes'))\n        dislike_count = int_or_none(gfy.get('dislikes'))\n        age_limit = 18 if gfy.get('nsfw') == '1' else 0\n\n        width = int_or_none(gfy.get('width'))\n        height = int_or_none(gfy.get('height'))\n        fps = int_or_none(gfy.get('frameRate'))\n        num_frames = int_or_none(gfy.get('numFrames'))\n\n        duration = float_or_none(num_frames, fps) if num_frames and fps else None\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/gfycat.py#L53-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import requests\nr = requests.get(f'https://api.gfycat.com/v1/gfycats/{gfy_id}')\nbody = r.json()\nif 'error' in body or 'gfyItem' not in body:\n    print('Gfycat unavailable:', body.get('error', 'unknown'))","typeGuard":"def gfy_ok(body):\n    return isinstance(body, dict) and 'error' not in body and isinstance(body.get('gfyItem'), dict)","tryCatchPattern":"try:\n    info = ydl.extract_info(url)\nexcept DownloadError as e:\n    if 'Gfycat said' in str(e):\n        skip_and_log(url, str(e))  # dead/private item — expected\n    else:\n        raise","preventionTips":["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"],"tags":["gfycat","api-error","dead-link","extractor"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}