Python requests
The standard way Python talks to APIs — including every hosted model service. This sheet covers requests (the classic) with httpx notes where they differ.
Table of Contents
Installation / Setup
pip install requests httpx # or: uv add requests httpx
import requests # synchronous, batteries included
import httpx # modern: sync + async, HTTP/2
Core Syntax
The five verbs
requests.get(url)
requests.post(url, json={...})
requests.put(url, json={...})
requests.patch(url, json={...})
requests.delete(url)
# httpx mirrors the API: httpx.get(url), httpx.post(url, json=...), ...
Response object
r = requests.post(api_url, json=payload)
r.status_code # 200
r.ok # True if < 400
r.headers # {'content-type': 'application/json', ...}
r.text # raw body as str
r.json() # parsed body (raises on invalid JSON)
r.raise_for_status() # raise HTTPError for 4xx/5xx
r.elapsed # time taken (timedelta)
r.url # final URL incl. query params
Common Operations
Query parameters
# dict form — requests encodes & escapes for you
params = {"q": "llm", "limit": 10, "offset": 20}
r = requests.get("https://api.example.com/v1/search", params=params)
r = requests.post(url,
json={"text": "great", "model": "v2"}, # body -> JSON, sets Content-Type
headers={"Authorization": f"Bearer {API_KEY}"}, # auth
timeout=30, # ALWAYS set
)
| Kwarg |
Purpose |
json= |
dict → JSON body (do this, not data=str) |
data= |
form-encoded or raw bytes |
params= |
query string dict |
headers= |
extra headers |
auth=(user, pass) |
basic auth |
cookies= |
session cookies |
files= |
multipart upload |
stream=True |
read body lazily (large responses) |
timeout= |
connect+read timeout in seconds |
Sessions (reuse connections & cookies)
with requests.Session() as s:
s.headers.update({"Authorization": f"Bearer {API_KEY}"})
for item in items:
resp = s.post(url, json=item, timeout=30) # same connection pool
resp.raise_for_status()
Auth options
# Bearer token (OpenAI, Anthropic, hosted models)
headers = {"Authorization": f"Bearer {API_KEY}"}
# Basic
requests.get(url, auth=("user", "pass"))
# httpx: httpx.Client(headers={...})
# requests OAuth1/OAuth2 -> use requests-oauthlib / authlib
Practical Examples
1. Call an LLM-style API
import requests
resp = requests.post(
"https://api.example.com/v1/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Summarize in 1 line."}],
"temperature": 0.0,
},
timeout=60,
)
resp.raise_for_status()
answer = resp.json()["choices"][0]["message"]["content"]
print(answer)
2. Error-aware prediction client
def predict(text: str, model: str = "v1") -> dict:
resp = requests.post(URL, json={"text": text, "model": model}, timeout=30)
if resp.status_code == 429: # rate limited
wait = float(resp.headers.get("Retry-After", 5))
time.sleep(wait)
return predict(text, model) # one retry
resp.raise_for_status()
return resp.json()
3. Download a large artifact to a file
r = requests.get(model_url, stream=True, timeout=(5, 60))
r.raise_for_status()
with open("model.pt", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
4. httpx async (parallel model calls)
import asyncio, httpx
async def main():
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(url, json={"text": "hello"})
print(resp.json())
asyncio.run(main())
Common Patterns
# Shared helpers
def api_call(method, path, **kw):
kw.setdefault("timeout", 30)
kw.setdefault("headers", {}).setdefault("Authorization", f"Bearer {API_KEY}")
r = requests.request(method, BASE_URL + path, **kw)
r.raise_for_status()
return r.json()
# Simple retry loop with backoff
for attempt in range(3):
try:
return requests.post(url, json=payload, timeout=30).json()
except (requests.Timeout, requests.ConnectionError) as e:
if attempt == 2:
raise
time.sleep(2 ** attempt)
Best Practices
- Always pass
timeout — the #1 cause of "my script hangs".
- Use
json= for JSON bodies — encoding and headers handled correctly.
- Check
r.ok / call raise_for_status() and handle 429/5xx explicitly.
- Use one
Session when making many calls (connection reuse, shared headers).
- Never put API keys in code or URLs — env vars (see environment-variables.md) + header auth.
- Prefer
httpx for async needs; requests is fine for sync scripts.
- Set a User-Agent identifying your app; some APIs require it.
- For big downloads,
stream=True and write chunks; don't load whole bodies.
Common Mistakes
| Mistake |
Consequence |
Fix |
No timeout |
Hangs forever on a dead API |
Always set it |
data=json.dumps(...) without header |
415 or wrong encoding |
json= kwarg |
| Ignoring status codes |
Silent wrong data |
raise_for_status() / handle 429 |
| Retrying instantly on 429 |
Rate-limit loop |
Retry-After + backoff |
| Storing keys in code |
Leak on commit |
Env vars / secrets |
.json() on an HTML error page |
JSONDecodeError |
Check status; use .text to debug |
| New connection per request |
Slow |
Session() |
stream=True forgotten for big files |
Memory blowup |
Stream + chunk |
Troubleshooting
| Symptom |
Cause |
Fix |
Max retries exceeded / connection error |
Wrong host/port, TLS, down |
curl -v the URL; check network |
JSONDecodeError: Expecting value |
Body isn't JSON (HTML 404/500) |
Print r.status_code, r.text[:500] |
| 401/403 |
Missing/expired credentials |
Check env var, token expiry, scopes |
ReadTimeout |
Server slow (big model) |
Longer timeout or async endpoint |
| SSL errors behind proxies |
Cert/proxy issue |
Set env HTTP_PROXY/HTTPS_PROXY; verify= only when truly needed |
| UnicodeDecodeError |
Wrong charset |
Use r.encoding = r.apparent_encoding or utf-8 requests |
Official Documentation
Last Updated
2026-09-06 — Initial version.