Skip to content

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

Headers that matter

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.