The shape of an API call
Every AI-generated script that talks to a service does roughly this:
import httpx
response = httpx.get("https://api.example.com/users/7")
response.raise_for_status() # crash on 4xx/5xx
data = response.json() # parse the body as JSON
print(data["name"])
Four lines. AI writes them on autopilot. Your job is to read them and catch the places they break: forgetting the status check, and indexing into the JSON with the wrong key.
A call is a request (you send) and a response (you receive). The
request has a method (GET to read, POST to create), a URL,
headers (Authorization, Content-Type), and — for POST — a
JSON body, which httpx takes as httpx.post(url, json={...}).
The response has a status code, headers, and a body. That
three-digit status is the most diagnostic field in the response, and
it's where this lesson starts.
A note on this chapter: the browser can't make real network calls. So instead of hitting a real URL, we'll work with hardcoded JSON shapes that match what an API would actually return. The pattern is the same; we just skip the wire.
The three status families
Only three families matter for everyday work, identified by the first digit of the code:
-
2xx— success.200 OKis the workhorse.201 Createdcomes back from a POST that made something new. Anything in the 200s means the request worked. -
4xx— client error. You are wrong.400 Bad Request(your JSON was malformed).401 Unauthorized(no/invalid auth).403 Forbidden(auth was valid, but you can't access this).404 Not Found(the URL or resource doesn't exist).429 Too Many Requests(you hit the rate limit). The common thread: don't retry blindly. Fix the request. -
5xx— server error. They are wrong.500 Internal Server Error,502 Bad Gateway,503 Service Unavailable,504 Gateway Timeout. Not your fault, and a retry might work in a few seconds.
The // 100 trick: integer-divide a status by 100 to get the family.
200 // 100 == 2, 404 // 100 == 4, 503 // 100 == 5. AI uses this
exact pattern when bucketing responses.
A worked example
The editor on the right routes three hardcoded fake responses using exactly that trick. Three iterations, three branches:
ok: {'id': 7, 'name': 'maya'}
client error: user not found
server error: service unavailable
This is the mental model: ignore the exact code, look at the family, decide the behavior.
Where AI specifically gets this wrong
One: ignoring the status code entirely. The script does
response.json() straight after the call. If the API returns 401
with a JSON error body, .json() actually succeeds and hands back
{"error": "..."} — then your downstream code asks for data["name"]
and dies with a KeyError six lines away from the real bug: nobody
checked the status.
Two: treating 4xx and 5xx the same. A single
if response.status_code != 200: return None hides the difference
between "the user doesn't exist" (a normal business case) and "the
upstream service is on fire" (a transient failure worth retrying).
Family-based branching is the move.
Three: no timeout. httpx.get(url, timeout=None) — or the older
requests.get(url), which has no default at all — means a hung server
hangs your script forever. Set it explicitly:
httpx.get(url, timeout=10.0).
Run the editor. Three fake responses get routed to three different branches based purely on the first digit of their status.