Quick Reference · a next-gen HTTP client · Python

httpx cheat sheet

requests' API, made Client-centric and async-ready. Same round trip — you build a request, a Client sends it over a pooled connection (HTTP/1.1 or HTTP/2), you read one Response — but the same code runs sync or async, timeouts are on by default, and redirects are off by default. Know those three twists and everything you learned from requests carries over.

setup / CLI the request the response the Client async transport · HTTP/2 · TLS gotcha most common

Introspected from httpx 0.28 & cross-checked across: python-httpx.org (official) · encode/httpx (GitHub) · betterstack.com · oneuptime.com · python-httpx.org/async · httpcore docs

One round trip — and the sync / async duality that defines httpx
BAND A · THE REQUEST / RESPONSE CYCLE, THROUGH A CLIENT Build a Request client.get(url, …) params= · headers= · auth= content= / data= / json= / files= timeout ON (5s) · redirects OFF Client connection pool (keep-alive) + Transport (httpcore) HTTP/1.1 or HTTP/2 base_url · shared headers / cookies / auth / limits Server handles the verb One Response r.status_code · r.http_version r.text r.json() r.content r.is_success · r.is_error r.raise_for_status() → HTTPStatusError .iter_bytes() / .aiter_bytes() to stream TCP/TLS BAND B · SAME API, TWO RUNTIMES — async JUST ADDS async with, await, aiter_ SYNC — httpx.Client with httpx.Client() as c: r = c.get("https://api.…") data = r.json() # blocks until the response arrives ASYNC — httpx.AsyncClient async with httpx.AsyncClient() as c: r = await c.get("https://api.…") data = r.json() # await lets other tasks run while it waits → identical Response object & identical method names ←
quickstart.py — the 90% you reach for
import httpx

# one-off (spins up + discards a client) — fine for a script
r = httpx.get("https://api.example.com/items",
              params={"page": 2},
              headers={"Authorization": "Bearer <tok>"},
              follow_redirects=True)   # OFF by default in httpx!
r.raise_for_status()                    # → HTTPStatusError on 4xx/5xx
data = r.json()

# reuse a Client for many calls: pooling + base_url + shared headers
with httpx.Client(base_url="https://api.example.com",
                 headers={"Authorization": "Bearer <tok>"},
                 timeout=10.0) as client:
    client.post("/items", json={"name": "Milo"})   # relative path joins base_url

# async: same surface, add async with / await
async with httpx.AsyncClient() as client:
    r = await client.get("https://api.example.com/items")
01Setup & Importpick your extras
02The HTTP Verbstop-level or client
03Query Parametersthe ?key=value string
04Sending a Body4 kwargs, pick one
05Custom Headersdict in, multidict out
06The Response Objecteverything on r
07Status & is_* Helpersno math needed
08Working with JSONthe API workhorse
09The Client (sync)use it, don't skip it
10Async — AsyncClientthe signature feature
11Timeoutson by default
12Redirects & HistoryOFF by default
13Errors & Exceptionstwo branches
14Authenticationprove who you are
15Streaming & Downloadsdon't buffer it all
16HTTP/2opt-in, one flag
17Transports & Retriesthe low level
18Cookiessend & persist
19Proxies & TLSroute & verify

Four pictures that make httpx click

The models behind the cards — the four body kwargs, the two-branch exception tree, the four timeout phases, and why a Client beats a bare httpx.get().

1 · Four body kwargs — pick exactly one

httpx splits raw content= from form data= (requests overloaded data= for both).

json={…} any JSON-able object application/json encoded + header set data={…} a dict of fields x-www-form-urlencoded form fields only content=… bytes / str / generator raw body, as-is you set Content-Type files={…} file-like objects multipart/form-data boundary handled passing raw bytes to data= is wrong — that slot is for form dicts

2 · The exception tree — two questions

Did you get a response at all? RequestError = no. HTTPStatusError = yes, but a bad status.

HTTPError RequestError "never got a response" HTTPStatusError from raise_for_status() TransportError Timeout… Network… Protocol… Timeout→ Connect/Read/Write/Pool · Network→ Connect/Read DecodingError TooManyRedirects catch broad or narrow except httpx.HTTPError → catches everything RequestError vs HTTPStatusError to split

3 · Four timeout phases of one request

httpx clocks each phase separately — all default to 5s. requests only had (connect, read).

time → pool wait for a free connection slot connect establish the TCP + TLS write send the request body read wait for / receive the response httpx.Timeout(connect=5, read=5, write=5, pool=5) one float sets all four · a Timeout() object tunes them independently

4 · httpx.get() vs a Client

The bare call builds and throws away a client — and its whole connection pool — every time.

httpx.get(url) ×3 — new client + pool each time client + pool dial → call → discard client + pool dial → call → discard client + pool dial → call → discard 3 handshakes with Client() as c: c.get() ×3 — one pool, reused Client + pool dial once c1 c2 c3 reused 1 handshake · keep-alive

Worth memorizing

redirects OFFfollow_redirects=False by default (requests: True!)
timeouts ON5s on every phase by default; timeout=None disables (risky)
content= vs data=content= raw bytes/str; data= form dict only
use a Clienthttpx.get() builds + discards a client (no pooling)
async = same APIAsyncClient + await + async with; aiter_* to stream
HTTP/2http2=True AND pip install httpx[http2] (the h2 pkg)
raise_for_statusraises HTTPStatusError; returns r so it chains
retriesHTTPTransport(retries=n) = connections only, not statuses
status retriesno built-in 5xx/429 retry — use tenacity
error splitRequestError = no response · HTTPStatusError = bad status
proxy= singularproxies= was removed in 0.28 → proxy= / mounts=
is_success / is_errorboolean helpers on the Response — skip the math
streaming.stream() ctx; .text mid-stream → ResponseNotRead
4 timeoutsTimeout(connect, read, write, pool)
base_urlset on a Client; relative paths join onto it
test in-processASGITransport / WSGITransport / MockTransport