{"record":{"id":"8862562f3a7ab345","repo":"BerriAI/litellm","slug":"unsupported-content-type-content-type","errorCode":null,"errorMessage":"Unsupported content type: {content_type}","messagePattern":"Unsupported content type: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/common_utils/http_parsing_utils.py","lineNumber":345,"sourceCode":"            data[key] = [(value.filename, file_content, value.content_type)]\n        else:\n            # Regular form field\n            data[key] = value\n    return data\n\n\nasync def get_request_body(request: Request) -> dict[str, Any]:\n    \"\"\"\n    Read the request body and parse it as JSON.\n    \"\"\"\n    if request.method == \"POST\":\n        content_type: Final = request.headers.get(\"content-type\", \"\")\n        if _is_json_content_type(content_type):\n            return await _read_request_body(request)\n        elif _is_form_content_type(content_type):\n            return await get_form_data(request)\n        else:\n            raise ValueError(f\"Unsupported content type: {content_type}\")\n    return {}\n\n\ndef extract_nested_form_metadata(form_data: dict[str, Any], prefix: str = \"litellm_metadata[\") -> dict[str, Any]:\n    \"\"\"\n    Extract nested metadata from form data with bracket notation.\n\n    Handles form data that uses bracket notation to represent nested dictionaries,\n    such as litellm_metadata[spend_logs_metadata][owner] = \"value\".\n\n    This is commonly encountered when SDKs or clients send form data with nested\n    structures using bracket notation instead of JSON.\n\n    Args:\n        form_data: Dictionary containing form data (from request.form())\n        prefix: The prefix to look for in form keys (default: \"litellm_metadata[\")\n\n    Returns:","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/common_utils/http_parsing_utils.py#L327-L363","documentation":"get_request_body() only accepts two content types for POST: JSON (parsed via _read_request_body) and form/multipart (via get_form_data). Anything else — text/plain, application/xml, application/octet-stream, or a custom type — hits the else branch and raises ValueError('Unsupported content type: {content_type}'). GET and other methods return {} without touching the body.","triggerScenarios":"POST to a proxy route that pre-reads the body while sending Content-Type: text/plain or application/xml. Also triggered by clients that omit or mangle the header so it parses as neither JSON nor form.","commonSituations":"Sending a prompt as raw text with the wrong header. Webhooks that post XML or form-urlencoded data (application/x-www-form-urlencoded is form-parsed only if _is_form_content_type matches; verify your type). A proxy chain rewrites the Content-Type.","solutions":["Send Content-Type: application/json with a JSON body for POST endpoints.","For file uploads, use multipart/form-data (let the HTTP client set it).","If a non-JSON client must integrate, convert the payload at an upstream adapter before it reaches the proxy."],"exampleFix":"# before\nrequests.post(url, data=\"tell me a joke\", headers={\"Content-Type\": \"text/plain\"})\n\n# after\nrequests.post(url, json={\"model\": \"gpt-4\", \"messages\": [{\"role\": \"user\", \"content\": \"tell me a joke\"}]})","handlingStrategy":"type-guard","validationCode":"SUPPORTED = (\"application/json\", \"multipart/form-data\", \"application/x-www-form-urlencoded\")\n\ndef content_type_supported(ct: str) -> bool:\n    return ct.split(\";\")[0].strip().lower() in SUPPORTED\n\nassert content_type_supported(headers[\"Content-Type\"])","typeGuard":"def is_supported_content_type(content_type: str) -> bool:\n    base = content_type.split(\";\")[0].strip().lower()\n    return base in {\"application/json\", \"multipart/form-data\", \"application/x-www-form-urlencoded\"}","tryCatchPattern":"try:\n    resp = session.post(url, data=raw, headers={\"Content-Type\": ct})\n    resp.raise_for_status()\nexcept ValueError as e:\n    if \"Unsupported content type\" in str(e):\n        resp = session.post(url, json=payload)  # resend as JSON\n    else:\n        raise","preventionTips":["Always send JSON bodies with the json= parameter of your HTTP client; it sets the header.","Add a wrapper around proxy calls that asserts the content type before sending.","For non-JSON integrations (webhooks), convert payloads in an adapter service upstream of the proxy."],"tags":["litellm","http","content-type","bad-request","header"],"backgroundTag":"unsupported-content-type","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}