HTTP: methods, status codes, headers, body
The protocol every API speaks, read as a conversation you can inspect.
HTTP is the protocol every web API speaks. It is text, it is simpler than its reputation, and you can read a whole exchange by eye — which is exactly what makes it debuggable.
Here is a real one, from this site:
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: application/json
Cache-Control: private, no-store
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
{"user":null}Every part of that is something you will set deliberately one day.
The four parts of a request
POST /api/orders HTTP/1.1 ← method, path, version
Host: api.example.com ← headers: metadata about the request
Content-Type: application/json
Authorization: Bearer eyJhbGc…
← a blank line, then
{"item": "book", "qty": 2} ← the bodyMethod — what kind of action. Path — which resource. Headers — everything about the request that is not the request. Body — the data, on the methods that carry one.
A response has the same shape with a status line instead of a request line.
Methods, and the two properties that matter
| Method | Means | Safe? | Idempotent? |
|---|---|---|---|
GET | read something | yes | yes |
POST | create something, or "do a thing" | no | no |
PUT | replace something entirely | no | yes |
PATCH | change part of something | no | usually not |
DELETE | remove something | no | yes |
Safe means it changes nothing. A GET that creates an order is a bug, and a serious one — browsers, proxies and crawlers all assume they may repeat a GET freely, and some will.
Idempotent means doing it twice has the same effect as doing it once. DELETE twice leaves the thing deleted. PUT twice leaves the same value. POST twice creates two orders — which is why a user double-clicking Submit, or a client retrying after a timeout, is a real and expensive problem. It has a whole lesson to itself later in the course; for now, notice that the property comes from the method's meaning, not from anything the protocol enforces.
Status codes by their first digit
You do not memorise the list. You learn the ranges, and look up the rest.
| Range | Meaning | The ones you will use |
|---|---|---|
| 2xx | it worked | 200 OK, 201 Created, 204 No Content |
| 3xx | look elsewhere | 301 permanent, 302/308 temporary or method-preserving |
| 4xx | the client got it wrong | 400, 401, 403, 404, 409, 422, 429 |
| 5xx | the server got it wrong | 500, 502, 503, 504 |
The 4xx/5xx boundary is the one that matters most and is most often got wrong. It is a statement about whose fault it is, and it drives alerting: a spike in 4xx means clients are sending bad requests, a spike in 5xx means you are broken. Returning 500 for a missing field trains everybody to ignore your alerts.
Two pairs worth separating now:
- 401 vs 403. 401 is I do not know who you are — sign in. 403 is I know who you are and you may not — signing in again will not help.
- 404 vs 400. 404 is there is nothing at this path. 400 is the path is fine, what you sent is not.
POST /api/orders. Method and path. The method says what KIND of action — and POST is neither safe nor idempotent, which is why sending it twice creates two orders.
headers. Everything ABOUT the request that is not the request. Content-Type says what the body is; Authorization says who is asking. The server can reject on these alone, without reading a byte of the body.
the body. The data. Only now does the server know what you actually want, and this is the first point at which validation can run.
server decides. Everything the client checked, it could also have skipped. Every rule that matters is enforced here — and the outcome becomes a number.
the status. The first digit says the category, and 4xx versus 5xx says WHOSE FAULT. Returning 500 for a missing field trains everybody to ignore your alerts.
Headers carry the interesting metadata
Content-Type: application/json what the body is
Authorization: Bearer eyJhbGc… who is asking
Accept: application/json what I would like back
Cache-Control: private, no-store who may keep a copy, and for how longTwo of those appear in the real response above. Cache-Control: private, no-store on /api/me is deliberate: this response is about you, and no proxy or browser should keep it where another person could be served it.
The response above also carries X-Content-Type-Options and X-Frame-Options — security headers, which exist because a browser will do dangerous things by default unless told not to. They come up properly in the security phase.
It is a request-response protocol, and that shapes everything
The client asks, the server answers, and the exchange is over. The server cannot push you something later on that connection.
Everything that looks like a server-initiated message — a notification, a live update — is built on top of that limitation: the client polls repeatedly, or holds a connection open, or uses a different protocol entirely. Knowing that the base is one-shot explains why "just push it to the client" is never as simple as it sounds.
Try it yourself
Read a real exchange
curl is the tool. -D - prints the response headers; -o /dev/null throws the body away when you only want them.
- Fetch any API and read its headers. What
Content-Typedid it send? - Request a path that does not exist. What status came back?
- Find a URL that redirects, and look at the
Locationheader. - Send a
POSTwith a JSON body to an API that expects one, and look at the status.
The commands, and what to notice
curl -s -D - -o /dev/null https://code10x.in/api/me
curl -s -o /dev/null -w '%{http_code}\n' https://code10x.in/no-such-page-here # 404
curl -s -D - -o /dev/null https://code10x.in// # 308 + Location
curl -s -X POST -H 'Content-Type: application/json' -d '{"a":1}' https://example.com/apiNotice that the 404 came back with a full HTML page in its body. A status code and a body are independent: an error status can carry a helpful explanation, and a 200 can carry a body saying something failed — which is a design mistake, but a common one.
Misconceptions
- "HTTPS is a different protocol." It is HTTP inside an encrypted tunnel. Same methods, same status codes, same headers — an observer sees which server you contacted and nothing more.
- "
POSTis for creating,PUTis for updating." Closer:PUTreplaces at a known location and is idempotent;POSTis the general "do something" and is not. The difference that matters is idempotency, not the verb's name. - "A 200 means it worked." It means the request was handled. A
200whose body says{"error": "..."}is a real and unfortunate pattern.