requests.exceptions.InvalidHeader: Header part invalid in Python Requests
Ensure all dictionary keys and values in headers are strictly Python str (or bytes), and remove invalid control characters like leading colons or newlines.
Root Cause Analysis
This error occurs when Python's requests or urllib3 library validates an HTTP request header dictionary, but one of the header names or values is not a string (such as an integer, boolean True, or None), or contains forbidden HTTP header byte characters like carriage returns (\r), newlines (\n), or leading colons.
HTTP Specification Header Rules (RFC 7230 / RFC 9110)
According to the HTTP protocol specifications, headers must consist strictly of US-ASCII strings. requests and urllib3 enforce strict type checking on header tuples before constructing the wire byte stream:
- Non-String Python Objects: Passing
headers={'Sec-GPC': 1}orheaders={'X-Cache-Enabled': True}fails because the integer1or booleanTruehas no defined HTTP wire encoding. - Copied HTTP/2 Pseudo-Headers: Copying raw headers from browser Developer Tools that include pseudo-headers starting with a colon (such as
:authority,:method,:path,:scheme). - Header Injection Control Characters: Values containing unescaped newline
\nor carriage return\rcharacters, which are blocked as HTTP response splitting / header injection vulnerabilities.
Reproduction Code (MCVE)
import requests
from requests.exceptions import InvalidHeader
# Simulates passing a non-string boolean header value in requests
raw_headers = {'Sec-GPC': True}
for key, val in raw_headers.items():
if not isinstance(val, (str, bytes)):
raise InvalidHeader(f"Header part (1) from ({repr(key)}, {repr(val)}) invalid: header value must be str or bytes, not {type(val).__name__}")
Solution 1: Explicitly Convert Header Values to Strings
Cast all header keys and values to str before passing the dictionary to requests.get() or requests.Session().
import requests
raw_headers = {
'User-Agent': 'PythonFix-Client/1.0',
'Sec-GPC': True,
'X-Request-ID': 10492,
'X-Timeout-Sec': 30.5
}
# Sanitize header dictionary: cast all keys and values to string
clean_headers = {str(k): str(v) for k, v in raw_headers.items() if not str(k).startswith(':')}
print(f'Sanitized headers ready for requests: {clean_headers}')
Solution 2: Strip HTTP/2 Pseudo-Headers Copied from Browser Tools
Filter out colon-prefixed pseudo-headers (:path, :authority) when pasting headers from browser devtools.
copied_browser_headers = {
':authority': 'api.example.com',
':method': 'GET',
':path': '/v1/users',
'Accept': 'application/json',
'Authorization': 'Bearer sample-token'
}
# Filter out pseudo-headers starting with ':'
valid_http1_headers = {
k: str(v) for k, v in copied_browser_headers.items()
if not k.startswith(':')
}
print(f'Valid HTTP/1.1 headers: {valid_http1_headers}')
Common Pitfalls & Edge Cases
Passing None as a header value (e.g. headers={'Authorization': None}) raises InvalidHeader. If you want to omit an optional header or remove a default session header, use del session.headers['Authorization'] or pass headers={'Authorization': None} only if using a custom wrapper that explicitly drops None keys.
Contrasting InvalidHeader with related errors:
requests.exceptions.InvalidHeader: Header contains non-str type or forbidden byte characters.requests.exceptions.InvalidURL: The URL scheme or domain name format is malformed.requests.exceptions.ConnectionError: The network socket failed to reach the server.