Skip to content

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)

JSON bodies & headers

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.