Area 08 // BackendLesson 125 / 14012 min read
HTTP and status codes
HTTP is the request-and-response language that browsers, apps and AI services use to talk to your backend, where each request has a method, path, headers and body, and each response carries a status code that says at a glance whether it worked and, if not, whose fault it was.
Think of it like this
Analogy
Ordering at a counter: you say what you want (the method and path: "GET a coffee"), show your loyalty card (headers), and maybe hand over a form (the body). The server replies with a short signal before the details: a thumbs up (200), "here's your new order number" (201), "I can't read your form" (400), "members only" (401), "we don't sell that" (404), "slow down, one at a time" (429), or "sorry, the machine broke" (500).
The problem it solves
Every AI app is mostly HTTP: your frontend calls your API, your API calls model providers, vector databases and tools, and webhooks call you. If you return the wrong status codes, clients can't tell a typo from an outage, retry the wrong things, or show useless error messages. If you misread the provider's codes, you hammer a rate-limited API or give up on a temporary blip.
How it works, step by step
- A request has a method (GET read, POST create or run something, PUT replace, PATCH update, DELETE remove), a path (
/documents/42), headers (Authorization,Content-Type,Accept) and an optional body, usually JSON. - A response has a status code, headers and a body.
- Status code families:
- 2xx success: 200 OK, 201 Created, 202 Accepted (work queued: async jobs), 204 No Content.
- 3xx redirects: the resource is elsewhere.
- 4xx the client's fault: 400 malformed, 401 not authenticated, 403 authenticated but not allowed, 404 not found, 409 conflict, 413 too large, 422 well-formed but invalid data (validation), 429 too many requests (rate limiting).
- 5xx the server's fault: 500 unexpected error, 502 bad gateway, 503 unavailable or overloaded, 504 timeout upstream.
- Use the right code so clients can react: fix input on 4xx, retry later on 429 and 5xx (retries), honour
Retry-After. - Return useful error bodies: a short message and an error code, never stack traces or secrets.
- Model providers follow the same rules: 400 for invalid requests, 401 for bad keys, 429 for rate limits, 5xx (or Anthropic's 529) when overloaded (provider rate limits).
- HTTPS encrypts all of this in transit; never send keys over plain HTTP.
See it in code
- Install
pip install fastapi httpx- Needs
- Python 3.10+. TestClient sends real HTTP-shaped requests to the app in memory.
A small API that returns the right status code for each situation.
from http import HTTPStatus
from fastapi import FastAPI, HTTPException, Header
from fastapi.testclient import TestClient
app = FastAPI()
DOCS = {"1": "Leave policy"}
@app.get("/docs/{doc_id}")
def get_doc(doc_id: str, authorization: str | None = Header(default=None)):
if authorization != "Bearer demo-token":
raise HTTPException(401, "missing or invalid token") # who are you?
if doc_id not in DOCS:
raise HTTPException(404, "no such document") # it doesn't exist
return {"id": doc_id, "title": DOCS[doc_id]}
@app.post("/ask", status_code=201)
def ask(body: dict):
if len(body.get("question", "")) < 3:
raise HTTPException(400, "question too short") # your request is wrong
if body["question"] == "quota":
raise HTTPException(429, "rate limit exceeded", headers={"Retry-After": "30"}) # slow down
return {"answer": "..."}
client = TestClient(app)
auth = {"Authorization": "Bearer demo-token"}
for method, path, kwargs in [
("GET", "/docs/1", {"headers": auth}),
("GET", "/docs/1", {}),
("GET", "/docs/9", {"headers": auth}),
("POST", "/ask", {"json": {"question": "What is RAG?"}}),
("POST", "/ask", {"json": {"question": "hi"}}),
("POST", "/ask", {"json": {"question": "quota"}}),
]:
r = client.request(method, path, **kwargs)
print(f"{method:4} {path:8} -> {r.status_code} {HTTPStatus(r.status_code).phrase:17} {r.json()}")GET /docs/1 -> 200 OK {'id': '1', 'title': 'Leave policy'}
GET /docs/1 -> 401 Unauthorized {'detail': 'missing or invalid token'}
GET /docs/9 -> 404 Not Found {'detail': 'no such document'}
POST /ask -> 201 Created {'answer': '...'}
POST /ask -> 400 Bad Request {'detail': 'question too short'}
POST /ask -> 429 Too Many Requests {'detail': 'rate limit exceeded'}In a browser's developer tools (Network tab) you can see the same method, status and headers for every request a web app makes.
A real example
Example
The 200 that hid every error
A chatbot API returned 200 OK for everything, with {"error": "..."} in the body when things failed. The mobile app's retry logic only looked at status codes, so provider outages showed users blank answers instead of "try again", and monitoring showed a perfect 0% error rate during a two-hour incident. Switching to proper codes (429 with Retry-After for quota, 503 when the provider was down, 422 for bad input) made the app's retries work, gave users sensible messages, and made the error-rate dashboard tell the truth.
The easy way
Start here
Use your framework's helpers: FastAPI picks 200 for normal returns, 422 for invalid bodies, and lets you raise HTTPException(status, message) for the rest. Learn the dozen codes in the cheat sheet; that covers almost everything.
When you grow: the production setup
Teams standardise error bodies (often RFC 9457 "problem details"), map internal exceptions to codes in one place, include request IDs in error responses, set Retry-After and rate-limit headers, never leak stack traces, track error rates by code in dashboards, and document codes per endpoint in the OpenAPI spec (FastAPI and REST).
Trade-offs
What you gain
- Clients can react correctly without parsing messages.
- Monitoring and alerts work on status codes.
- Shared language across every tool and library.
What it costs you
- Some cases are ambiguous (400 vs 422, 401 vs 403).
- Status codes alone don't explain the error; you still need good messages.
- Streaming responses have already sent 200 before a mid-stream failure.
When not to use it
- Never return 200 for failures in a REST API.
Common mistakes
- Returning 200 with an error message in the body.
- Using 500 for bad user input.
- Mixing up 401 (not authenticated) and 403 (not allowed).
- Retrying 400s, or retrying 429s immediately.
- Putting API keys in URLs, where they end up in logs.
Interview questions
Try answering out loud first, then open the model answer.
Explain the main HTTP status code classes with examples.
Model answer
2xx success (200 OK, 201 Created, 202 Accepted, 204 No Content); 3xx redirects (301, 302, 304); 4xx client errors (400 bad request, 401 unauthenticated, 403 forbidden, 404 not found, 409 conflict, 422 validation error, 429 rate limited); 5xx server errors (500 internal error, 502 bad gateway, 503 unavailable, 504 gateway timeout).
How should a client handle errors from an LLM provider?
Model answer
Treat 4xx (except 429) as non-retryable: fix the request, key or permissions. For 429, wait for Retry-After or back off exponentially with jitter. For 5xx and overload errors, retry a limited number of times with backoff, then fail over to another model or return a friendly error. Log status codes and request IDs.
What's the difference between 401 and 403?
Model answer
401 Unauthorized means the request isn't authenticated (missing, invalid or expired credentials), so logging in may help. 403 Forbidden means the server knows who you are but you don't have permission for this action or resource.
Quiz
Pick an answer to see whether it’s right and why.
0 of 5 answered
Key terms
- HTTP
- The request-and-response protocol of the web. A client sends a method, path, headers and optional body; the server replies with a status code, headers and a body.
- HTTP method
- The verb of a request, such as GET (read), POST (create or run), PUT (replace), PATCH (update) or DELETE (remove).
- Status code
- The three-digit number in an HTTP response saying how it went, such as 200 OK, 404 Not Found, 429 Too Many Requests or 500 Internal Server Error.
- HTTP header
- A name and value sent with a request or response to carry metadata, such as Authorization, Content-Type or Retry-After.
- JSON
- A simple text format for structured data (objects, lists, strings, numbers), used by almost every web API and by LLM structured outputs.
Before and after
Progress is saved in this browser only.