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 self

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Inspect resp.status and resp.headers['Content-Type'] before calling .json().
  2. Pass content_type=None to disable the check when you know the body is JSON regardless of type: await resp.json(content_type=None).
  3. Pass the actual expected content type, e.g. content_type='application/vnd.api+json'.
  4. 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

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


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/d3f3bd3bc5a56115.json. Report an issue: GitHub.