Request & Response Anatomy: Headers, Bodies, and Everything In Between
Every HTTP request has the same parts. Once you know them, every API you'll ever test becomes readable.
A request has five parts
Every HTTP request, regardless of API or protocol flavor, has the same five parts:
- Method — the verb (GET, POST, PUT, PATCH, DELETE).
- URL — where to send it, including the path and any query parameters.
- Headers — metadata about the request (auth tokens, content type, request IDs, user agent).
- Body — the payload (for POST, PUT, PATCH). Optional for GET/DELETE.
- Protocol version — almost always HTTP/1.1 or HTTP/2; rarely something you care about as a tester.
Let's build one out piece by piece, then run it.
Method
Just the verb: POST, in this case. Covered in detail in the HTTP methods lesson.
URL
A URL has more parts than most people realize:
https://demo.totalshiftleft.ai/api/v1/products?category=electronics
└──┬──┘ └──────────┬──────────┘└─────┬─────┘└──────────┬──────────┘
scheme host (domain) path query string
- Scheme —
https(always use HTTPS in production;httpis for local dev only). - Host — the server's domain name.
- Path — the resource you're targeting. REST convention: plural nouns, hierarchical (
/users/123/orders). - Query string — key/value pairs after
?, separated by&. Used for filtering, sorting, pagination, and feature flags. Covered in the pagination lesson.
Headers
Headers are the unsung heroes of APIs. They carry:
- Authentication:
Authorization: Bearer eyJhbGc... - Content negotiation:
Content-Type: application/jsontells the server how to parse your body;Accept: application/jsontells it what format you want back. - Tracing:
x-request-idand similar headers let logs and distributed tracing tools stitch together requests across services. - Caching:
Cache-Control,ETag,If-None-Match— how clients and CDNs know whether to reuse a previous response. - Session state: in this sandbox, an
x-session-idheader scopes your data.
Header names are case-insensitive (Content-Type and content-type are equivalent). Values are case-sensitive.
Body
For methods that send data (POST, PUT, PATCH), the body contains the payload. The body format is declared in the Content-Type header. The three formats you'll see:
application/json— the 2026 default. JSON is human-readable, universally supported, and parses into native objects in every language.application/x-www-form-urlencoded— classic HTML form data. Still used for OAuth token exchanges.multipart/form-data— for file uploads and mixed-content forms.
If the Content-Type and the actual body don't match, you'll get a 400 or a very confused parser. This is a real bug category — always assert on Content-Type in tests.
Seeing a full request
Click Run. Notice the request we build: POST to /api/v1/products, with a custom x-request-id header, Content-Type, and a JSON body.
/api/v1/productscurl -X POST 'https://demo.totalshiftleft.ai/api/v1/products' \
-H 'x-request-id: demo-123' \
-H 'Content-Type: application/json' \
-d '{"name":"Laptop","price":1299.99,"category":"electronics","stock":10}'A response has four parts
Responses mirror requests:
- Status code — covered in the status codes lesson.
- Status message — the human-readable version of the code (
OK,Created,Not Found). Tools largely ignore this; the code is what matters. - Headers — response metadata.
- Body — the payload.
Response headers worth inspecting
Content-Type— tells you how to parse the body. Don't assume JSON; always check.Content-Length— size of the body in bytes.Cache-Control— how long the client can reuse this response.ETag— a fingerprint of the response. Send it back asIf-None-Matchto ask "has anything changed?" — the server responds304 Not Modifiedif not.x-ratelimit-*— many APIs expose rate-limit state (x-ratelimit-remaining,x-ratelimit-reset) in custom headers.Location— after creating a resource (201 Created), points to the new resource's URL.
What to assert in a test
A complete API test typically asserts on:
- Status code — always.
- Key response headers —
Content-Typeat minimum;Locationfor POSTs; custom tracing/rate-limit headers when relevant. - Response body shape — required fields present, types correct.
- Response body content — specific values (the created user's name matches what you sent).
- Response time — under a threshold (e.g., p99 < 500ms).
Skipping any of these leaves holes. Asserting only on status code is the classic junior mistake: the endpoint returns 200 with an empty body and your test passes.
Common mistakes
1. Treating headers as cosmetic. Auth lives in headers. Tracing lives in headers. Rate limits live in headers. If you ignore headers in your tests, you miss an entire class of bugs.
2. Logging bodies at INFO level. Response bodies can contain PII, tokens, or payment data. Log headers (excluding auth) freely; log bodies only at DEBUG and scrubbed.
3. Forgetting Content-Type on POSTs. If you don't send Content-Type: application/json with a JSON body, many servers will 400 or, worse, try to parse it as form data. Tests should verify that the header is required — send a request without it and assert on the error.
What's next
You now understand what a request and response look like. Next up: query params, pagination, sorting, and filtering — the glue that makes lists of data usable.
Related lessons
GET, POST, PUT, PATCH, DELETE — the five verbs that carry 99% of API traffic. Here's what each one means, with runnable examples.
2xx means success, 4xx means you messed up, 5xx means they messed up — but the details matter. Here's the list every tester should know by heart.
Every useful API returns lists of things. Here's how to page through, sort, and filter them — with runnable examples.