Quick Reference · HTTP for Humans · Python

requests cheat sheet

Every call is one round trip: you build a request (verb + URL + params / headers / body / auth), a Session sends it over a pooled connection, and you get back one Response — read it via .status_code, .headers, and .text / .json() / .content. Learn that shape once and the API stops being a list to memorize.

setup / import the request the response session · headers · auth advanced TLS · files · I/O gotcha most common

Verified 2026-08-26 against requests 2.34.2 & cross-checked across: requests.readthedocs.io (official) · realpython.com · scrapeops.io · findwork.dev · python-requests.org · urllib3 docs

One round trip — and the Session that powers it
BAND A · THE REQUEST / RESPONSE CYCLE You build a Request requests.get(url, …) verb · url · params= headers= · auth= data= / json= / files= ↓ prepared into a PreparedRequest Server handles the HTTP verb returns status + body You get one Response r.status_code · r.ok · r.headers r.textdecoded str r.json()dict/list r.contentbytes .iter_stream r.raise_for_status() · r.elapsed · r.url send → over TCP / TLS ← respond BAND B · WHAT A SESSION ADDS (AND WHY IT’S FASTER) Session s = requests.Session() persists across calls: cookies · headers · auth s.mount(prefix, adapter) s.get(…) · s.post(…) HTTPAdapter mounted per URL prefix "https://" , "http://" holds max_retries=Retry(…) pool_connections / pool_maxsize Connection Pool urllib3 keeps sockets open Keep-Alive → reuse the TCP+TLS handshake instead of redialing = big speed-up on repeat calls requests.get() = a throwaway one-shot Session (no reuse, no shared cookies)
quickstart.py — the 90% you reach for
import requests

# GET with a query string  →  /search?q=cats&page=2
r = requests.get("https://api.example.com/search",
                 params={"q": "cats", "page": 2},
                 headers={"Authorization": "Bearer <token>"},
                 timeout=10)            # ALWAYS set a timeout

r.raise_for_status()             # turn 4xx/5xx into an exception
data = r.json()                 # parse the JSON body → dict / list

# POST JSON (sets Content-Type: application/json for you)
r = requests.post("https://api.example.com/items",
                  json={"name": "Milo", "kind": "cat"},
                  timeout=10)
print(r.status_code, r.ok)      # 201 True
01Setup & Importinstall once
02The HTTP Verbsone function each
03Query Parametersthe ?key=value string
04Sending a Bodydata vs json vs files
05Custom Headersdict in, dict out
06The Response Objecteverything on r
07Status & raise_for_statuserrors don't raise
08Working with JSONthe API workhorse
09Cookiessend & receive
10Redirects & Historythe 3xx trail
11Timeoutsnever omit this
12Errors & Exceptionsall under one root
13Session Objectsreuse & persist
14Authenticationprove who you are
15Retries & Adaptersrequests won't retry
16Streaming & Downloadsdon't load it all
17File Uploadsmultipart/form-data
18TLS / SSL & Proxiesverify & route
19Prepared Requests & Hooksthe escape hatch

Four pictures that make the API click

The mental models behind the cards — the request/response shape, how the body kwarg picks the Content-Type, the exception tree, and the many views of one response body.

1 · Anatomy of a round trip

What you put in, and what you get back — every call is this same shape.

REQUEST GETthe verb https://host/path params= → ?q=x&n=2 headers= → User-Agent… auth= → who you are json= / data= / files=     the body payload timeout= → give-up clock RESPONSE (r) 200 OKr.status_code r.headers → server meta r.text → decoded str r.json() → dict / list r.content → bytes r.ok · r.url · r.elapsed r.raise_for_status()

2 · Which body kwarg sets which Content-Type

The kwarg you choose decides how the body is encoded — the top source of "why is my POST empty?".

json={…} a JSON-able object application/json encoded + header set for you data={…} a dict x-www-form-urlencoded like an HTML <form> files={…} file-like objects multipart/form-data boundary handled for you data=json.dumps(x) → sends JSON but sets NO header · json= is ignored if data=/files= present

3 · The exception hierarchy

Everything descends from RequestException — catch broad or narrow as you like.

RequestException ConnectionError Timeout HTTPError SSLError ProxyError ConnectTimeout ReadTimeout (also a ConnectionError) TooManyRedirects JSONDecodeError catch RequestException to handle any failure at once

4 · One body — four ways to read it

The same bytes come back once; each accessor is a different lens over them.

bytes on the wire gzip / deflate maybe r.content raw bytes (decompressed) r.text bytes decoded via r.encoding → str r.json() parsed → dict / list r.iter_content() chunk-by-chunk needs stream=True r.raw undecoded stream

Worth memorizing

timeout=no default → a call can hang forever. Always set it.
4xx/5xx don't raisecheck r.ok or call r.raise_for_status()
json= vs data=json= sets the JSON header; data=json.dumps() does not
json= droppedignored if data= or files= is also passed
r.json() can throwJSONDecodeError when the body isn't JSON (HTML errors!)
no auto-retrymount an HTTPAdapter(max_retries=Retry(…))
allowed_methodsurllib3 v2 renamed method_whitelist → old sheets break
status_forcelistempty by default — Retry ignores statuses unless you list them
Session > bare getreuses connections (keep-alive) + shares cookies/headers
verify=Falseinsecure & only warns — keep TLS verification on
text vs content.text guesses encoding; .content is exact bytes
stream=Trueor the whole body loads into RAM before you touch it
HEAD signuponly HEAD defaults allow_redirects=False
cross-host redirectauth & cookie headers are stripped for safety
sync onlyneed async / HTTP2? reach for httpx or aiohttp
params list{"id":[1,2]} repeats the key → ?id=1&id=2