HTTP Essentials
The protocol underneath every API call your models serve or consume. Knowing HTTP means knowing what your requests.post(...) and fetch(...) actually do — and debugging failures fast.
Table of Contents
Installation / Setup
Inspect HTTP with curl (installed everywhere) or Python:
curl -i https://api.example.com/v1/health # headers + body
import requests
r = requests.get("https://api.example.com/v1/health")
print(r.status_code, r.headers, r.json())
Core Syntax
Request anatomy
POST /v1/predict HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer sk-...
User-Agent: my-app/1.0
{"text": "great product", "model": "v2"}
| Part |
Meaning |
| Method |
the verb (below) |
| Path |
which resource (/v1/predict) |
| Headers |
metadata: content type, auth, caching |
| Body |
payload (usually JSON) |
Methods
| Method |
Purpose |
Idempotent |
Typical use |
GET |
read |
yes |
fetch predictions list, health |
POST |
create / run action |
no |
inference (/predict), create record |
PUT |
replace entirely |
yes |
update config/record |
PATCH |
partial update |
no |
change one field |
DELETE |
remove |
yes |
delete record |
HEAD |
headers only |
yes |
existence checks |
OPTIONS |
what's allowed |
yes |
CORS preflight |
ML convention: model inference is a POST to something like /predict or /v1/classify — it runs an action and (usually) isn't idempotent.
Common Operations
Status codes (memorize the families + common ones)
| Code |
Meaning |
Fix/action |
| 200 |
OK |
✅ |
| 201 |
Created |
POST success |
| 204 |
No content |
delete success |
| 301 / 308 |
Moved permanently |
update URL |
| 400 |
Bad request |
fix payload/schema |
| 401 |
Unauthenticated |
add/refresh token |
| 403 |
Forbidden |
insufficient permission |
| 404 |
Not found |
wrong path/resource |
| 409 |
Conflict |
duplicate/state clash |
| 422 |
Unprocessable |
validation failed (FastAPI!) |
| 429 |
Too many requests |
back off; retry-after |
| 500 |
Server error |
check server logs |
| 502 / 503 / 504 |
Gateway/unavailable/timeout |
upstream or overloaded |
if r.status_code == 200:
return r.json()
elif r.status_code == 429:
time.sleep(float(r.headers.get("Retry-After", 5))) # polite retry
else:
r.raise_for_status() # raises for 4xx/5xx
| Header |
Meaning |
Content-Type |
body format (application/json) |
Accept |
format you want back |
Authorization: Bearer <token> |
auth |
Content-Length |
body size |
Cache-Control, ETag |
caching |
Retry-After |
when to retry (429/503) |
Access-Control-Allow-Origin |
CORS policy |
X-Request-ID |
trace a request through logs |
Query strings & paths
GET /v1/search?q=llm&limit=10&offset=20&model=fast
path ────┘ └── query parameters ──────────────
params = {"q": "llm", "limit": 10, "model": "fast"}
r = requests.get(url, params=params) # requests URL-encodes for you
Practical Examples
1. Call a model API from Python
import requests
resp = requests.post(
"https://api.example.com/v1/predict",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"text": "great product", "model": "v2"}, # sets JSON body
timeout=30, # always set!
)
resp.raise_for_status()
prediction = resp.json()
print(prediction["label"], prediction["score"])
2. Serve an endpoint (FastAPI — see the API sheet for detail)
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Request(BaseModel):
text: str
model: str = "v1"
@app.post("/predict")
def predict(req: Request):
result = run_model(req.text, req.model) # your inference
return {"label": result.label, "score": result.score} # -> JSON 200
FastAPI auto-returns 422 with a readable error when the JSON body doesn't match the schema.
3. Call from the browser (CORS-aware)
const res = await fetch("http://localhost:8000/predict", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "hello" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
Common Patterns
# Session reuse (keep-alive, shared headers)
with requests.Session() as s:
s.headers.update({"Authorization": f"Bearer {API_KEY}"})
r1 = s.post(url, json=...)
r2 = s.get(url)
# Retry with backoff (polite to rate limits)
import time
for attempt in range(3):
r = requests.post(url, json=payload, timeout=30)
if r.status_code == 429 and attempt < 2:
time.sleep(2 ** attempt); continue
r.raise_for_status(); break
Best Practices
- Always set
timeout on requests — a hung API hangs your code forever.
- Check status codes and raise/handle explicitly; don't assume success.
raise_for_status() after requests; handle 429 with backoff.
- Send and expect
application/json; use json= in requests so encoding is right.
- Never log full tokens/API keys; use
Authorization headers, not query strings.
- Keep endpoints idempotent where possible (PUT/GET), and POST for actions.
- Serve with structured errors: JSON
{"detail": "..."} (FastAPI-style) — clients can parse them.
- Understand CORS before blaming the browser: servers must opt in via
Access-Control-Allow-Origin.
Common Mistakes
| Mistake |
Consequence |
Fix |
| No timeout |
Requests hang forever |
timeout=30 |
| Ignoring status codes |
Silent wrong behavior |
Check + raise/handle |
data= string with manual json.dumps |
Encoding/header bugs |
json= kwarg |
Key in URL query (?key=...) |
Leaks into logs/history |
Header-based auth |
Forgetting Accept/Content-Type |
Wrong format responses |
Set both |
| Retrying immediately on 429 |
Rate-limit hammering |
Respect Retry-After + jitter |
| Serving HTML error pages to APIs |
Client can't parse |
JSON error responses |
| Browser blocks request |
CORS misconfig |
Add Access-Control-Allow-Origin server-side |
Troubleshooting
| Symptom |
Cause |
Fix |
requests.exceptions.ConnectionError |
Server down / wrong URL / TLS |
Verify URL; curl -v; check port |
| 401 / 403 |
Missing or wrong auth |
Check token header, expiry, scopes |
| 404 on valid-looking path |
Trailing slash/base path |
Match exactly; check docs/OpenAPI |
| 422 validation error |
Body/schema mismatch |
Read the error detail; check field types/names |
| CORS error in browser only |
Missing server CORS header |
Configure CORS middleware (FastAPI CORSMiddleware) |
ReadTimeout on big inference |
Slow model |
Raise timeout, stream, or async job |
| 429 despite few calls |
Shared rate limit |
Check X-RateLimit-* headers |
Official Documentation
Last Updated
2026-09-06 — Initial version.