aio-libs/aiohttp · error · ContentTypeError

Attempt to decode JSON with unexpected mimetype

Error message

Attempt to decode JSON with unexpected mimetype: %s

What it means

Raised as ContentTypeError (a subclass of ClientResponseError) from ClientResponse.json() when the response's Content-Type does not match the expected type. By default content_type='application/json' and the check is lenient (is_expected_content_type allows charset/suffix variants), but a clearly non-JSON mimetype (e.g. text/html) triggers the error. Pass content_type=None to disable the guard.

Solutions

  1. Inspect resp.status and resp.content_type before calling .json(); handle non-2xx explicitly.
  2. If you know the body is JSON regardless, disable the check: await resp.json(content_type=None).
  3. Fix the server to send 'Content-Type: application/json'.
  4. Catch ContentTypeError and fall back to manual parsing or logging.

Example fix

# before
async with session.get(url) as resp:
    data = await resp.json()  # ContentTypeError if server sends text/html

# after — accept any content type
data = await resp.json(content_type=None)
# or guard explicitly:
if resp.status != 200:
    text = await resp.text()
    raise RuntimeError(f'server error: {text}')
data = await resp.json(content_type=None)
Defensive patterns

Strategy: try-catch

Validate before calling

async def safe_json(resp):
    if not resp.content_type.startswith('application/json'):
        # disable the strict check or handle manually
        return await resp.json(content_type=None)
    return await resp.json()

Type guard

def looks_like_json(resp) -> bool:
    ct = resp.content_type.lower()
    return ct.startswith('application/json') or ct.endswith('+json')

Try / catch

from aiohttp import ContentTypeError

try:
    data = await resp.json()
except ContentTypeError:
    text = await resp.text()
    log.warning('Non-JSON response (status=%s, ct=%s): %s', resp.status, resp.content_type, text[:200])
    # optionally parse manually if you know it's JSON despite the header
    import json
    data = json.loads(text)

Prevention

When it happens

Trigger: Calling await resp.json() on a response whose Content-Type is not JSON-compatible (e.g. 'text/html' from an error page, 'text/plain', 'application/xml'). The is_expected_content_type check fails and ContentTypeError is raised after the body is read.

Common situations: API returning an HTML error page (500/502) instead of JSON; server mis-labeling JSON as text/plain; proxy/gateway injecting an HTML error; endpoint that returns JSON but with a wrong Content-Type header.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/d3f3bd3bc5a56115. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)