encode/httpx · error · TypeError

Header value must be str or bytes, not {type(value)}

Error message

Header value must be str or bytes, not {type(value)}

What it means

TypeError raised by _normalize_header_value when a header value is neither str nor bytes. httpx headers are normalized to bytes, so any other type (int, None, dict, list, custom object) is rejected. This fires during Headers construction or any header mutation that funnels through _normalize_header_value (e.g. client.headers['X-Count'] = 5).

Source

Thrown at httpx/_models.py:81

        return False
    return True


def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes:
    """
    Coerce str/bytes into a strictly byte-wise HTTP header key.
    """
    return key if isinstance(key, bytes) else key.encode(encoding or "ascii")


def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes:
    """
    Coerce str/bytes into a strictly byte-wise HTTP header value.
    """
    if isinstance(value, bytes):
        return value
    if not isinstance(value, str):
        raise TypeError(f"Header value must be str or bytes, not {type(value)}")
    return value.encode(encoding or "ascii")


def _parse_content_type_charset(content_type: str) -> str | None:
    # We used to use `cgi.parse_header()` here, but `cgi` became a dead battery.
    # See: https://peps.python.org/pep-0594/#cgi
    msg = email.message.Message()
    msg["content-type"] = content_type
    return msg.get_content_charset(failobj=None)


def _parse_header_links(value: str) -> list[dict[str, str]]:
    """
    Returns a list of parsed link headers, for more info see:
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
    The generic syntax of those is:
    Link: < uri-reference >; param1=value1; param2="value2"
    So for instance:

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Coerce to str: client.headers['X-Count'] = str(count).
  2. For None, use a guard: headers['X-Opt'] = value if value is not None else ''.
  3. For lists, join: ', '.join(values).
  4. For datetimes, .isoformat() or format explicitly.

Example fix

// before
client.headers['X-Retry-Count'] = retries  # int -> TypeError
// after
client.headers['X-Retry-Count'] = str(retries)
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_header_value(value):
    if isinstance(value, (str, bytes)):
        return value
    if value is None:
        return ''
    return str(value)

headers = {k: coerce_header_value(v) for k, v in raw_headers.items()}

Type guard

def is_valid_header_value(value) -> bool:
    return isinstance(value, (str, bytes))

Try / catch

try:
    client.headers['X-Count'] = count
except TypeError:
    client.headers['X-Count'] = str(count)

Prevention

When it happens

Trigger: Setting a header to an int (X-Total: 5), float, None, bool, or list; passing a Headers object/dict whose values are non-string; client(headers={'X-Retry': 3}); response.headers manipulation with numeric counters.

Common situations: Dynamic header values computed from numeric data without str(); None passed where a string was expected (e.g. missing config); booleans from toggles; CSV lists meant to be joined; datetime objects not isoformatted.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/c6a02126cc4ae39c.json. Report an issue: GitHub.