aio-libs/aiohttp · warning · ContentTypeError
Attempt to decode JSON with unexpected mimetype: %s
Error message
Attempt to decode JSON with unexpected mimetype: %s
What it means
Raised as ContentTypeError (a ClientResponseError subclass) in ClientResponse.json() when content_type checking is enabled (the default) and the response's Content-Type header does not match the expected type. By default json() expects 'application/json'; this guard prevents silently parsing HTML error pages or text bodies as JSON.
Source
Thrown at aiohttp/client_reqrep.py:757
if encoding is None:
encoding = self.get_encoding()
return self._body.decode(encoding, errors=errors) # type: ignore[union-attr]
async def json(
self,
*,
encoding: str | None = None,
loads: JSONDecoder = DEFAULT_JSON_DECODER,
content_type: str | None = "application/json",
) -> Any:
"""Read and decodes JSON response."""
await self.read()
if content_type:
if not is_expected_content_type(self.content_type, content_type):
raise ContentTypeError(
self.request_info,
self.history,
status=self.status,
message=(
"Attempt to decode JSON with "
"unexpected mimetype: %s" % self.content_type
),
headers=self.headers,
)
if encoding is None:
encoding = self.get_encoding()
return loads(self._body.decode(encoding)) # type: ignore[union-attr]
async def __aenter__(self) -> "ClientResponse":
self._in_context = True
return selfView on GitHub (pinned to c0ef574e29)
Solutions
- Inspect resp.status and resp.headers['Content-Type'] before calling .json().
- Pass content_type=None to disable the check when you know the body is JSON regardless of type: await resp.json(content_type=None).
- Pass the actual expected content type, e.g. content_type='application/vnd.api+json'.
- Fix the server to return a correct JSON Content-Type.
Example fix
# before data = await resp.json() # raises if server sent text/html # after (opt out of the check) data = await resp.json(content_type=None) # or be specific data = await resp.json(content_type='application/vnd.api+json')
Defensive patterns
Strategy: try-catch
Validate before calling
ct = resp.headers.get('Content-Type', '')
if 'json' not in ct.lower():
# log / handle unexpected content type before parsing
text = await resp.text()
else:
data = await resp.json() Try / catch
from aiohttp import ContentTypeError
try:
data = await resp.json()
except ContentTypeError:
# server returned non-JSON (often HTML error page)
text = await resp.text()
raise MyApiError(f'unexpected body: {text[:200]}') Prevention
- Check resp.status and Content-Type before parsing.
- Pass content_type=None if you intentionally accept any type.
- Log response body snippets on ContentTypeError for diagnosis.
When it happens
Trigger: Fires at line 755-766 when content_type is truthy (default 'application/json') and is_expected_content_type() returns False. Common when a server returns text/html for an error (404/500), text/plain, or a vendor type like application/vnd.api+json not covered by the matcher.
Common situations: API returns 200 with text/html (e.g. a login page behind a redirect); reverse proxy returns an error page; vendor JSON content types; server sets charset in a way that defeats matching in older aiohttp.
Related errors
- Connection closed
- Cannot compute fallback encoding of a not yet read body
- Security issue: Digest auth challenge contains empty 'nonce'
- Digest auth error: Unsupported Quality of Protection (qop) v
- total timeout must be a positive number or None to disable,
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/d3f3bd3bc5a56115.json.
Report an issue: GitHub.