Anatomy of an HTTP Message
Designing an API well starts with seeing exactly what goes over the wire. An HTTP exchange is just text: a request from the client and a response from the server, each with the same three parts.
The request
Section titled “The request”A request has a start line (method + target + HTTP version), a set of headers, and an optional body:
POST /articles HTTP/1.1Host: api.example.comContent-Type: application/jsonAuthorization: Bearer eyJhbGci...Accept: application/json
{ "title": "Designing APIs", "body": "..." }The method (POST) says what to do; the target (/articles) says to what; the headers carry metadata (content type, auth, what the client accepts); the body carries the payload.
The response
Section titled “The response”The response mirrors that shape: a status line (version + status code + reason), headers, and an optional body:
HTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /articles/42
{ "id": 42, "title": "Designing APIs" }flowchart LR
subgraph Request
A[Start line: method + path] --> B[Headers] --> C[Body optional]
end
subgraph Response
D[Status line: code + reason] --> E[Headers] --> F[Body optional]
end
Request -->|over HTTP| Response Building a response in code
Section titled “Building a response in code”Server frameworks hide most of this, but it helps to construct a response by hand once. This snippet builds the headers and JSON body for a “created” response and logs them:
| Advantages (HTTP Structure) | Trade-offs |
|---|---|
| A universal standard — every client knows how to read a request/response | More verbose than a binary protocol like gRPC |
| Text-based — human-readable, easy to debug with curl | Header overhead on every request — HTTP/2 mitigates this with header compression |
| Stateless — every request carries all the information it needs | Auth has to be sent on every request, unlike session-based auth |
| Cacheable — browsers and CDNs can cache based on headers | Misconfigured caching can leave clients seeing stale data |
Common mistakes
Section titled “Common mistakes”Putting sensitive data in the URL instead of a header Symptom:
- Sending a token in the query string:
GET /users?token=abc123 - The token ends up in logs and browser history
- Always use
Authorization: Bearer <token>in a header
Not setting Content-Type Symptom:
- Sending a JSON body without
Content-Type: application/json - The server parses the body wrong, or rejects the request
- Set
Content-Typeon every request that has a body
💡 Real-world examples
GitHub API:
- Recommends
Accept: application/vnd.github+jsonon every request- Tells the server the client wants the latest JSON response shape
Stripe API:
- Uses an
Idempotency-Keyheader on sensitivePOSTrequests- Prevents a duplicate charge when the client retries after a network error
Sources: GitHub REST API media types, Stripe idempotent requests