{"record":{"id":"70e10ef2bced8fba","repo":"xtekky/gpt4free","slug":"response-must-be-a-dict-or-have-headers","errorCode":null,"errorMessage":"Response must be a dict or have headers","messagePattern":"Response must be a dict or have headers","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"g4f/image/copy_images.py","lineNumber":83,"sourceCode":"    return str(int(timestamp)) + \"_\" + filename.split(\"_\", maxsplit=1)[-1]\n\n\nasync def save_response_media(\n    response,\n    prompt: str,\n    tags: list[str] = None,\n    transcript: str = None,\n    content_type: str = None,\n) -> AsyncIterator:\n    \"\"\"Save media from response to local file and return a response object.\"\"\"\n    if isinstance(response, dict):\n        content_type = response.get(\"mimeType\", content_type or \"audio/mpeg\")\n        transcript = response.get(\"transcript\")\n        response = response.get(\"data\")\n    elif hasattr(response, \"headers\"):\n        content_type = response.headers.get(\"content-type\", content_type)\n    elif not content_type:\n        raise ValueError(\"Response must be a dict or have headers\")\n\n    if isinstance(response, str):\n        response = base64.b64decode(response)\n\n    extension = MEDIA_TYPE_MAP.get(content_type)\n    if extension is None:\n        raise ValueError(f\"Unsupported media type: {content_type}\")\n\n    filename = get_filename(tags, prompt, f\".{extension}\", prompt)\n    if hasattr(response, \"headers\"):\n        filename = update_filename(response, filename)\n    target_path = os.path.join(get_media_dir(), filename)\n    ensure_media_dir()\n\n    with open(target_path, \"wb\") as f:\n        if isinstance(response, bytes):\n            f.write(response)\n        else:","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f/image/copy_images.py#L65-L101","documentation":"Thrown by g4f.image.copy_media() (copy_images.py) when the response argument is neither a dict (with 'data'/'mimeType' keys) nor an object with .headers (e.g. an aiohttp/requests response), and no explicit content_type was passed. The function must know the media's MIME type to pick an extension and filename; with none of the three sources available it aborts.","triggerScenarios":"copy_media(b'...mp3 bytes...') with content_type=None; copy_media('base64str') without content_type (str responses are decoded but still need a type); passing a custom response wrapper that exposes .content but not .headers.","commonSituations":"Adapters that already extracted bytes/str from a provider response and forward only the payload; newer provider return types (raw bytes) passed to a helper written around dict/curl_cffi responses.","solutions":["Pass the MIME type explicitly: copy_media(data, content_type='audio/mpeg', ...).","Or pass the original response dict {'data': ..., 'mimeType': ...} if the provider returned one.","Or pass the response object itself (with .headers) instead of pre-extracted bytes."],"exampleFix":"// before\nreturn_media = await copy_media(audio_bytes, prompt=prompt, tags=tags)\n\n// after\nreturn_media = await copy_media(audio_bytes, prompt=prompt, tags=tags, content_type=\"audio/mpeg\")","handlingStrategy":"validation","validationCode":"def copy_media_ready(response, content_type) -> bool:\n    return (\n        isinstance(response, dict)\n        or hasattr(response, \"headers\")\n        or content_type is not None\n    )","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass content_type when forwarding raw bytes or base64 strings.","Prefer forwarding the provider's original response object (with .headers) instead of pre-extracted payloads."],"tags":["media","input-validation","content-type"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}