underlay
module 045–7h

How the Web Works & APIs

HTTP end-to-end, and testing APIs like a pro

HTTP is a conversation with strict etiquette. Your browser (or curl, or your code) sends a REQUEST — method, path, headers, optional body — and the server answers with a RESPONSE — status code, headers, body. That's it. Every API, every website, every agent tool call is this same conversation.

Once you can read the two sides of the conversation, 'the API doesn't work' becomes 'the API returned 422 because I sent a string where it wanted a number'.

Reading a real response, headers and all

`curl -i` shows you the whole conversation. This is a real capture against a public API — note the status line, the caching header, and the rate-limit headers that tell you how polite you must be:

bashcurl -i — see the full response
curl -i https://jsonplaceholder.typicode.com/todos/1

HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: max-age=43200
etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s"
x-ratelimit-limit: 1000
x-ratelimit-remaining: 999

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Status codes are the headline: 2xx = it worked, 3xx = go look over there, 4xx = you (the client) did something wrong, 5xx = the server broke. The ones you'll see daily: 200, 201, 204, 301, 400, 401 (not logged in), 403 (not allowed), 404, 409, 422 (validation), 429 (slow down), 500, 502, 503, 504. When nginx sits in front of your app, 502 means 'the app behind me died'.

JWT: header.payload.signature — and why it's not secret

A JWT is three base64-encoded JSON parts joined by dots. Anyone can DECODE it — it is not encrypted. The signature is what matters: the server recomputes HMAC over the header and payload with its secret, and if it doesn't match, the token is rejected. That's how a token can be 'readable by anyone but forgeable by no one'.

pythondecode a JWT with zero libraries (base64 is in stdlib)
executes in a sandbox
import base64, json

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzQyIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzg1NTk2ODg0fQ.signature"

def b64decode(part: str) -> dict:
    pad = part + "=" * (-len(part) % 4)          # base64url needs padding
    return json.loads(base64.urlsafe_b64decode(pad))

print(b64decode(token.split(".")[0]))   # {"alg": "HS256", "typ": "JWT"}
print(b64decode(token.split(".")[1]))   # {"sub": "user_42", "role": "admin",
                                        #  "exp": 1785596884}
# NEVER put passwords in a JWT payload. It's base64, not encryption.

Test APIs like a professional: hermetic, repeatable, in CI

Manual testing with curl is for exploring. Automated testing is for staying sane when an AI changes your API. The pattern: mock every external API at the transport level so tests never touch the network — fast, deterministic, and runnable in CI:

pythontest_users.py — pytest + httpx + respx
import httpx, respx

@respx.mock
def test_fetch_user_never_hits_the_network():
    # register a fake response for the external API
    respx.get("https://api.example.com/users/42").mock(
        return_value=httpx.Response(200, json={"name": "Ada"})
    )

    r = httpx.get("https://api.example.com/users/42")
    assert r.status_code == 200
    assert r.json()["name"] == "Ada"
    assert respx.calls.call_count == 1   # exactly one request, mocked

And when you hand an API to an agent: FastAPI generates OpenAPI docs at /docs automatically. An agent that reads the spec writes correct calls on the first try — that's the real 'brief the agent' move.

test yourself

01A 422 from an API means:

02A JWT is:

03Why does your browser get a CORS error while curl works fine?

the real check

bru run passes in CI. A mocked test proves no live network in your test suite.

0 stamps earned·streak: 0 days

Signed out: progress lives in this browser. to carry it everywhere.

vibe-coder payoff

80% of vibe coding is wiring APIs together. This removes the magic.