HTTP & APIs in 10 Minutes

Every app you've ever used is two computers passing notes. HTTP is the note format — and it's so simple you can read it raw. This page: the request/response model, what the status codes are actually telling you (401 vs 403, settled forever), REST without the theology, and the truth about the CORS error.

01The model: two notes, then silence

All of HTTP is one exchange: the client sends a request (a structured note), the server sends back a response (another note), and then — this is the part people miss — the server forgets you existed. HTTP is stateless. Every "logged-in experience" you've ever had is built by re-showing an ID card (a cookie or token) with every single note.

## the request — literally this readable:
GET /users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbG...

## the response:
HTTP/1.1 200 OK
Content-Type: application/json

{"id": 42, "name": "Ada"}
Why statelessness matters to you
It's why your session can "expire" mid-action, why an API needs the key on every call (not just the first), and why load balancers work at all — any server can answer any note, since no note depends on a previous one. When auth "randomly stops working", the ID card stopped being attached, not the server's memory.

02Anatomy of a URL

A URL is an address with four working parts: the protocol, the building (host), the room (path), and the sticky notes on the door (query parameters). Learn to read them and API docs stop being scary.

https://api.example.com/users/42/orders?status=paid&limit=10
└─┬──┘  └──────┬──────┘└──────┬──────┘ └────────┬─────────┘
scheme        host           path         query params
(how)       (which server)  (which thing)  (options: key=value, &-separated)
The rule people learn the hard way
Query params are logged everywhere — server logs, proxies, browser history, analytics. That's why secrets never go in URLs: ?api_key=sk_live_... is a key you should already consider leaked. Secrets ride in headers (section 07), which don't get logged by default.

03Methods: the verb on the envelope

The method tells the server what kind of note this is. Five cover everything: GET reads, POST creates, PUT replaces, PATCH edits a part, DELETE deletes. The concept behind them worth actually understanding: idempotency — can I safely send this twice?

GET    /users/42          # read — never changes anything
POST   /users             # create a new one  ⚠ twice = two users
PUT    /users/42          # replace entirely — twice = same result
PATCH  /users/42          # change some fields
DELETE /users/42          # delete — twice = still deleted
Why idempotency is a real-life concept
"Don't click Pay twice" is an idempotency bug. GET, PUT, DELETE are safe to retry; POST is not — which is why payment APIs make you send an Idempotency-Key header, so a retried request creates one charge, not two. When a network times out mid-POST, "did it go through?" is the whole reason this word exists.

04Status codes: who messed up

The first digit is the whole story: 2xx worked, 3xx moved, 4xx you messed up, 5xx they messed up. That one sentence sorts every API error you'll ever see into "fix my request" vs "wait / report it". And the eternal confusion pair, settled:

200 OK               # here you go
201 Created          # made it (answer to a good POST)
301 Moved            # new address, go there
400 Bad Request      # your note is malformed (bad JSON, missing field)
401 Unauthorized     # WHO ARE YOU? — no/invalid credentials
403 Forbidden        # I know who you are. No. — valid login, no rights
404 Not Found        # no such thing at this path
429 Too Many Requests # slow down (rate limit — wait and retry)
500 Internal Error   # server crashed. not your fault
503 Unavailable      # server overloaded/down. also not your fault
401 vs 403, once and forever
401 = the bouncer can't read your ID (missing, expired, or malformed token → check your auth header). 403 = your ID is fine, you're just not on the list (→ check permissions/scopes, not the token). The names are backwards ("Unauthorized" actually means unauthenticated) — a 50-year-old naming accident everyone just lives with.

05Headers & body: the envelope and the letter

Headers are metadata on the envelope — what format, what language, who's asking, what credentials. The body is the letter inside — for APIs, almost always JSON. You'll set two headers constantly and read a third:

Content-Type: application/json    # "the body I'm SENDING is JSON"
Authorization: Bearer <token>     # "here's my ID card"
Accept: application/json          # "please REPLY in JSON"

# the body (POST /users):
{ "name": "Ada", "country": "UK" }
You will hit this
You POST perfect JSON and get 400 Bad Request or a weird null-everything — because you forgot Content-Type: application/json, and the server parsed your JSON as a form submission. The body was fine. The label on the envelope was wrong. This one header explains a decade of Stack Overflow questions.

06curl: talk to any API from the terminal

curl sends HTTP requests from the command line. It's how you test an API without your app in the way — the single best debugging move: if curl works and your code doesn't, the bug is in your code; if curl fails too, it's the API. Divide and conquer.

# read
curl https://api.example.com/users/42

# create (-X method, -H header, -d body)
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "Ada"}'

# see the STATUS CODE and headers too (the debugging view)
curl -i https://api.example.com/users/42

07Auth: API keys and bearer tokens

Two patterns cover most APIs. An API key is a long-lived password for a program. A bearer token is usually short-lived, obtained by logging in, and presented as Authorization: Bearer … — "bearer" literally means whoever carries this, is this. Which is the security model and the security warning in one word.

# both ride in the Authorization header:
Authorization: Bearer eyJhbGciOi...     # token (often a JWT)
x-api-key: sk_live_4242...              # some APIs use a custom header

# keys live in environment variables, never in code:
curl -H "Authorization: Bearer $API_TOKEN" ...
The three commandments
Never in URLs (logged everywhere — section 02). Never in git (scrapers find it in minutes — see the Git page for the incident response). Never in frontend code (view-source = leaked). Keys live in environment variables and secret managers, and the moment one might have leaked, you rotate it first and investigate second.

08REST, without the theology

REST is a naming convention, not a technology: URLs name things (nouns), methods say what to do to them (verbs). That's 90% of it. An API is "RESTful" when you can guess the next endpoint without reading the docs — predictability is the entire point.

# nouns in the URL, verbs in the method:
GET    /articles          # list articles
POST   /articles          # create one
GET    /articles/7        # read one
PATCH  /articles/7        # edit it
DELETE /articles/7        # remove it
GET    /articles/7/comments   # nested: its comments

✗ POST /getArticleById   ✗ GET /deleteUser?id=7  # verbs in URLs = smell
Honest take
You'll meet people who argue about "true REST" (HATEOAS, Roy Fielding's dissertation…). Smile and move on. In practice "REST API" means exactly what this section shows: predictable noun-URLs + standard verbs + JSON. That vocabulary gets you through every real codebase and interview answer you'll need.

09CORS: the error that isn't what it looks like

One day your frontend calls an API and the console screams. Here's the twist everyone misses: this error is not the server rejecting you — it's your own browser, enforcing a safety rule: JavaScript from site A may not read responses from site B unless B explicitly allows it (via an Access-Control-Allow-Origin header). The proof: the same request works fine in curl.

Access to fetch at 'https://api.other.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present...

# decode: api.other.com didn't say localhost:3000 may read it.
# the browser fetched the data — then withheld it from your JS.
Who can actually fix it
Only the server can fix CORS (by sending the allow header) — no amount of frontend code legitimately makes it go away. Your options: it's your API → add the header for your frontend's origin; it's someone else's → call it from your backend and let your frontend call you (backends aren't browsers; CORS doesn't apply to them). Browser "CORS-disable" extensions fix your machine, then break for every real user.

10Cheat sheet

The mental tables that answer 90% of API questions.

# methods
GET read · POST create (⚠ not retry-safe) · PUT replace
PATCH edit · DELETE remove

# status: first digit = who messed up
2xx ✓ · 3xx moved · 4xx you · 5xx them
401 who are you? → fix the token
403 you specifically? no. → fix the permissions
429 slow down → wait and retry

# the POST that "mysteriously" fails
→ did you send Content-Type: application/json ?

# debugging order
curl -i first. code second.
curl works + code fails → your bug
curl fails too        → their bug (or your token)

# CORS error in console
→ browser rule, server must allow it. curl proves the API works.

# secrets
headers ✓ · env vars ✓ · URLs ✗ · git ✗ · frontend ✗

That's the working core. Everything fancier — webhooks, GraphQL, gRPC — is a variation on the same two notes. When you're ready to call APIs from code, Python plus this page is a complete toolkit.