beginner·8 min read·Updated May 1, 2026

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:

  1. Method — the verb (GET, POST, PUT, PATCH, DELETE).
  2. URL — where to send it, including the path and any query parameters.
  3. Headers — metadata about the request (auth tokens, content type, request IDs, user agent).
  4. Body — the payload (for POST, PUT, PATCH). Optional for GET/DELETE.
  5. 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
  • Schemehttps (always use HTTPS in production; http is 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/json tells the server how to parse your body; Accept: application/json tells it what format you want back.
  • Tracing: x-request-id and 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-id header 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.

POST/api/v1/products
Inspect a POST request with headers, body, and the response that comes back.
curl -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:

  1. Status code — covered in the status codes lesson.
  2. Status message — the human-readable version of the code (OK, Created, Not Found). Tools largely ignore this; the code is what matters.
  3. Headers — response metadata.
  4. 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 as If-None-Match to ask "has anything changed?" — the server responds 304 Not Modified if 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:

  1. Status code — always.
  2. Key response headersContent-Type at minimum; Location for POSTs; custom tracing/rate-limit headers when relevant.
  3. Response body shape — required fields present, types correct.
  4. Response body content — specific values (the created user's name matches what you sent).
  5. 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

Read more on the blog