httpx headers: request, client default and response headers (tested)

Pass headers={...} to httpx.get(), or to httpx.Client(headers=...) for defaults that merge with per-request headers. Read r.headers["content-type"] (case-insensitive) and r.headers.get_list("set-cookie") for repeated headers.

Set request headers in httpx with headers= on a single call or on a Client / AsyncClient as defaults; read response headers from r.headers. Measured with httpx 0.28.1 on Python 3.13.5 against a local http.server (srv.py) that echoes back the request headers it received and returns two Set-Cookie headers.

1. httpx get headers (one request)

import httpx
from srv import start
base = start()

r = httpx.get(base + "/", headers={"X-Api-Key": "k1"})
print(r.json())

Output:

{'method': 'GET', 'path': '/', 'content-type': None, 'body': '', 'User-Agent': 'python-httpx/0.28.1', 'X-Api-Key': 'k1'}

2. httpx client headers: default headers for every request

import httpx
from srv import start
base = start()

with httpx.Client(base_url=base, headers={"X-Api-Key": "k1", "User-Agent": "myapp/1.0"}) as client:
    print(client.get("/").json())
    # per-request headers merge; same key overrides
    print(client.get("/", headers={"X-Trace": "t1", "x-api-key": "k2"}).json())
    # change defaults later
    client.headers["X-Api-Key"] = "k3"
    print(client.get("/").json())

Output:

{'method': 'GET', 'path': '/', 'content-type': None, 'body': '', 'User-Agent': 'myapp/1.0', 'X-Api-Key': 'k1'}
{'method': 'GET', 'path': '/', 'content-type': None, 'body': '', 'User-Agent': 'myapp/1.0', 'X-Api-Key': 'k2', 'X-Trace': 't1'}
{'method': 'GET', 'path': '/', 'content-type': None, 'body': '', 'User-Agent': 'myapp/1.0', 'X-Api-Key': 'k3'}

Client headers are merged with per-request headers, and a per-request header with the same name overrides the default. Names are case-insensitive (x-api-key replaced X-Api-Key). client.headers is mutable, so you can rotate a key without rebuilding the client.

3. httpx default headers

import httpx
from srv import start
base = start()

with httpx.Client() as client:
    print(dict(client.headers))

Output:

{'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'user-agent': 'python-httpx/0.28.1'}

These four are sent unless you override them. Set User-Agent on the client to identify your app; some APIs and CDNs block the default python-httpx/x.y agent.

4. httpx response headers

import httpx
from srv import start
base = start()

r = httpx.get(base + "/")
print(type(r.headers).__name__)
print(r.headers["content-type"], "|", r.headers["Content-Type"])
print(r.headers.get("x-missing"))
print(r.headers["set-cookie"])
print(r.headers.get_list("set-cookie"))
print(r.request.headers["user-agent"])

Output:

Headers
application/json | application/json
None
a=1, b=2
['a=1', 'b=2']
python-httpx/0.28.1

r.headers is a case-insensitive httpx.Headers. Indexing a header that appears more than once (like Set-Cookie) joins the values with ", "; use get_list() to get them separately. r.request.headers shows what was actually sent.

5. Header values must be strings

import httpx
from srv import start
base = start()

for v in [123, None]:
    try:
        httpx.get(base + "/", headers={"X-Trace": v})
    except TypeError as e:
        print(repr(v), "-> TypeError:", e)

Output:

123 -> TypeError: Header value must be str or bytes, not <class 'int'>
None -> TypeError: Header value must be str or bytes, not <class 'NoneType'>

An int or None header value raises TypeError before the request is sent. Convert numbers with str(), and leave optional headers out of the dict instead of setting them to None.

6. httpx asyncclient headers

import asyncio, httpx
from srv import start
base = start()

async def main():
    async with httpx.AsyncClient(base_url=base, headers={"Authorization": "Bearer abc"}) as client:
        r = await client.get("/")
        print(r.json())

asyncio.run(main())

Output:

{'method': 'GET', 'path': '/', 'content-type': None, 'body': '', 'Authorization': 'Bearer abc', 'User-Agent': 'python-httpx/0.28.1'}

For auth, a client-level Authorization header works; httpx also has auth= for Basic/Digest auth.

Related: httpx params, httpx post json vs data, httpx vs requests differences, httpx follow redirects.

Frequently asked questions

How do I set headers in httpx?

Pass headers={'X-Api-Key': 'k'} to httpx.get()/post(), or to httpx.Client(headers=...) to send them on every request.

What default headers does httpx send?

accept: */*, accept-encoding: gzip, deflate, connection: keep-alive, user-agent: python-httpx/<version> (httpx 0.28.1).

How do I get response headers in httpx?

r.headers is a case-insensitive mapping: r.headers['content-type'], r.headers.get('x-name'), and r.headers.get_list('set-cookie') for repeated headers.

Why does httpx raise TypeError: Header value must be str or bytes?

A header value was an int or None. Convert numbers with str() and omit optional headers instead of setting them to None.

Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5 against a local http.server that echoes the request headers. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.