HTTP & Build Your First API
- Dissect an HTTP request and response: method, path, headers, status, body
- Choose correct methods and status codes for CRUD-style operations
- Use httpx to call APIs with params, headers, timeouts, and error handling
- Build a FastAPI service with path/query parameters and pydantic validation
- Explore your own API through its auto-generated docs
| Spaced-rep warm-up: due cards (async, GIL, semaphores) | 10 min |
| ELI5 + tech read; trace one request through the http-cycle visualizer | 18 min |
| Guided: build the counter, then be the customer | 42 min |
| Practice: the resilient client | 20 min |
| Project: quotes API + client | 20 min |
| Quiz + flashcards | 10 min |
Builds on: Day 40 โ Concurrency & async ยท Day 10 โ OOP II โ dataclasses (pydantic's shape) ยท Day 5 โ JSON handling
A coffee counter has a protocol everyone silently knows. You state a VERB and a THING: "GET me a latte." The barista replies with a STATUS before anything else โ "coming up" (all good), "we don't have that" (your mistake), "the machine is broken" (their mistake) โ and then, maybe, the drink. You can also hand things over: "POST: here's my custom order form." Neither of you remembers the last exchange; every order is complete in itself, which is why you repeat "for Priya, oat milk" every single time.
HTTP is exactly this, written down. The request line says the verb and the thing (GET /books/7); headers are the sticky notes on the order ("I speak JSON", "I'm Priya"); the body is the form you handed over. The response leads with a three-digit status โ 2xx coming-up, 4xx your-mistake, 5xx our-mistake โ then its own headers and body. Statelessness is the counter's amnesia: every request must carry everything needed to serve it.
Today you stand on BOTH sides of the counter: morning as the customer (httpx, the caller), afternoon as the barista (FastAPI, the server) โ and the customer half is how you will talk to every LLM API for the rest of the program.
HTTP is the substrate of your entire future stack: every LLM call (Day 106) is an HTTP POST, every 429 you retry (Day 107), every webhook, every RAG service endpoint. Building the server side is just as core: the AI features you ship are consumed as APIs, and FastAPI is the Python default โ async-native (yesterday's loop, now serving requests), pydantic-validated (the same contract thinking as Day 36's schemas, at the door of your service), with free interactive docs your customers and teammates will actually use. The Day 42 checkpoint, the Day 49 URL shortener, and the capstone's serving layer are all today's skeleton, thickened.
Ordering at the counter โ one HTTP request, end to end
step 1 / 6You ask for a URL. Before anything can be ordered, the browser needs the server's street address โ domain names are for humans, not routers.
Guided practice
Stand up the counter (FastAPI)
22 min- Install the day's tools in your venv:
pip install fastapi uvicorn httpx. Createapi.pyfrom the starter โ an in-memory books API (dict for storage; real persistence arrives tomorrow). - Run
uvicorn api:app --reloadand open http://127.0.0.1:8000/docs. Click through the auto-docs: expand GET /books, try it, read the curl it generated. This page came entirely from your type hints. - Poke the validation without writing any client code: try GET /books/abc (422 โ path type failed), POST a book with a negative price (422 with a field-level error message locating exactly what and where), then a valid POST (201, echoing the created resource with its id).
- Read the starter's status-code choices out loud and justify each: 201 for create, 404 for missing id, 422 arriving free from pydantic. Then add a DELETE route yourself returning 204 โ and confirm in /docs that it appeared.
- Watch the uvicorn console as you click: every line is method, path, status โ the request log you will grep in production (Day 16 pays off here).
Be the customer (httpx)
20 min- With the server still running, create
client.py. Use onehttpx.Client(base_url=..., timeout=5.0)for all calls (connection reuse โ and a timeout, always). - POST three books, then GET the list with
params={"author": ..., "limit": 2}โ print the URL httpx actually built (response.request.url) to see query-string encoding done for you. - Handle failure tiers properly: GET /books/999 and branch on status_code โ 404 is not an exception at transport level, and blind
.json()["title"]on it would KeyError. Then useraise_for_status()in a try/except as the alternative style; print status_code and the error body from the exception. - Inspect a full exchange: print request method/URL/headers and response status/headers (find content-type and content-length). Match each piece to the request-anatomy diagram from the tech section.
- Finish with the async version: rewrite the three POSTs with
httpx.AsyncClientandasyncio.gatherโ yesterday's pattern, today against a real server. Time sequential vs gathered.
On your own
The resilient client
20 minMake your API flaky, then build the client that survives it. Add GET /flaky to api.py: 30% of calls return 503, 20% return 429 with a Retry-After: 1 header, the rest 200. Then write resilient.py: a get_with_retries(client, url, max_tries=4) function implementing exponential backoff with jitter (0.5s base, doubling, plus random 0โ0.3s), honoring Retry-After when present, retrying ONLY retryable statuses (429, 502, 503 โ never 404), and logging each attempt with its wait. Prove it: 50 calls through the wrapper should all eventually succeed; print the attempt histogram.
Constraints: timeouts on every call; a final failure after max_tries raises a clear exception. Think: why is retrying a GET always safe, and when would retrying a POST double-charge someone?
Hints: wait = base * 2**attempt + random.uniform(0, 0.3); Retry-After arrives as seconds in r.headers.
Quotes API โ designed, built, documented
Design and build quotes_api.py solo: a quotes service with POST /quotes (text, author, tags list โ validated: non-empty text, at most 5 tags), GET /quotes (filterable by author and tag via query params, with limit/offset pagination), GET /quotes/{id}, DELETE /quotes/{id}, and GET /quotes/random. Correct status codes throughout (201/204/404/422). Then ship quotes_client.py exercising every route including the failure paths, using one Client with timeout. Screenshot or describe your /docs page in the README section. In-memory storage is fine โ tomorrow it grows a real database and tests. Commit both files.
Common mistakes & misconceptions
- No timeout on HTTP calls. The default is to wait forever; one hung upstream hangs your service. timeout= on every client โ non-negotiable from today.
- Assuming a 2xx because no exception was raised. httpx returns 404s and 500s as normal responses โ check status_code or call raise_for_status().
- Using GET for state-changing actions ("GET /books/7/delete"). Crawlers, prefetchers, and retries will happily fire it repeatedly โ verbs carry contracts.
- Returning 200 with {"error": "not found"} in the body. Clients branch on status codes; lying with 200 breaks every generic client, cache, and monitor downstream.
- Blocking inside an async def handler (sync DB call, time.sleep) โ yesterday's cardinal sin, now freezing every concurrent request your server is juggling.
- Retrying non-idempotent POSTs blindly on timeout โ the request may have succeeded before the timeout hit: double-charge. Retry idempotent calls freely; POSTs need idempotency keys (Day 107).
Q1. A client sends valid JSON to your FastAPI route, but price is -5 against Field(ge=0). What does FastAPI return?
Q2. Which request is safe to retry automatically after a timeout, and why?
Q3. Your service calls a dependency that starts returning 503. The well-behaved client response isโฆ
Go deeper โ curated resources
- FastAPI โ async def vs def routes โ โ Read how FastAPI runs plain-def handlers in a threadpool but async handlers on the loop โ then connect it to yesterday's blocking-call sin and decide which your DB calls need tomorrow.
- Books API serving with /docs explored; DELETE route added and verified
- Client handles 404 and validation-failure branches without crashing
- Resilient wrapper survives 50 flaky calls with logged backoff
- Quotes API + client committed with correct status codes throughout
- Quiz โฅ 2/3
โ Back: Handlers run on Day 40's event loop (its cardinal sin now a serving outage); pydantic models are Day 10's dataclasses with Day 36's contract enforcement; the JSON bodies are Day 5's serialization on the wire.
Forward โ: Tomorrow this API gains a real database and TestClient tests. Day 43 opens the network layers beneath it, Day 44 adds auth, Day 45 hardens the architecture โ and Day 106's LLM calls are exactly today's httpx POSTs with an Authorization header.
Unlocks: D42 Week 6 Checkpoint: API + DB Mini-Service ยท D43 Networking โ Packets to HTTPS ยท D44 Security, AuthN & AuthZ ยท D68 Cleaning & Validation