Every API request is built from the same handful of pieces: a URL with parameters in the path, query parameters after the question mark, headers carrying metadata, and (for write operations) a body containing the payload. Each piece has its own conventions, its own validation rules, and its own family of bugs. This lesson maps where each parameter type belongs, when to use which, and the test cases that catch the bugs unique to each.
Where the parameters live
A real request that uses all four:
PATCH /api/users/123/orders/9012?notify=true HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJI...
{"status": "shipped"}Read it left to right: path identifies the resource (user 123's order 9012), query refines behaviour (notify=true), headers carry metadata, body holds the change.
Path parameters
Path parameters identify the resource. They're part of the URL path itself, usually denoted in docs with curly braces:
/users/{userId}
/users/{userId}/orders/{orderId}
You substitute real values in the request:
GET /users/123
GET /users/123/orders/9012
Tests for path parameters
For every path parameter, cover at least these cases:
| Scenario | Expected |
|---|---|
| Valid existing id | 200 with that resource |
| Valid format, doesn't exist | 404 Not Found |
Wrong type (/users/abc when id is numeric) | 400 Bad Request |
Empty id (/users/) | 404 or 405 — endpoint mismatch |
Special characters (/users/../admin) | safely handled, not 500 |
| Very long id (1000+ chars) | 400 or 414 URI Too Long |
| Id of resource the caller can't access | 403 (or 404 to hide existence) |
That last one is a security-sensitive choice: returning 403 confirms the resource exists but you can't see it; returning 404 is more conservative. Different APIs make different choices — confirm with the team and test for the choice they made, not the one you'd prefer.
Query parameters
Query parameters refine how the request is interpreted. They live after the ? and are joined by &:
/api/users?role=admin&page=2&limit=10&sort=name
Common uses:
- Filtering —
?status=active&country=UK - Pagination —
?page=1&limit=20 - Sorting —
?sort=createdAt&order=desc - Field selection —
?fields=id,name,email - Search —
?q=alice
Tests for query parameters
For each documented query parameter:
| Scenario | Expected |
|---|---|
| Valid value | filtered/paginated result |
| Missing optional param | sensible default (page=1, limit=20) |
| Missing required param | 400 |
Invalid value (limit=ten) | 400 |
Out-of-range value (limit=99999) | 400 or capped silently |
Unknown param name (?floomp=xyz) | ignored or 400 (depends on API style) |
Repeated param (?role=admin&role=user) | handled consistently — first, last, or array |
URL-encoded special chars (?q=hello%20world) | decoded correctly |
The "repeated param" test catches a class of bug where a request like ?status=open&status=closed produces unpredictable results — some servers take the first, some the last, some treat it as an array, some 500.
URL encoding
Special characters in URLs must be percent-encoded:
- Space →
%20(or sometimes+in query strings) &→%26=→%3D?→%3F- Unicode chars (
café) → encoded byte-by-byte
Most clients (curl, Postman, language libraries) encode automatically when you pass parameters as data structures. They don't encode when you build a URL string by hand:
# Wrong — & in the value will be misinterpreted
curl "https://api.example.com/users?name=Alice&Bob"
# Right — let curl encode the value
curl --get https://api.example.com/users --data-urlencode "name=Alice&Bob"A test worth running once: send a query value containing every "interesting" character (& = ? # / + space emoji). If any breaks the API, you've found an encoding bug.
Request body
For POST, PUT, and PATCH requests, the data goes in the body. The Content-Type header tells the server how to parse it.
The two formats you'll meet:
- JSON (
Content-Type: application/json) — modern default for REST APIs.{"name": "Alice", "email": "alice@test.com"} - Form-encoded (
Content-Type: application/x-www-form-urlencoded) — older, what HTML forms submit.name=Alice&email=alice%40test.com
Multipart (multipart/form-data) is used for file uploads — covered in the next lesson.
Tests for request bodies
| Scenario | Expected |
|---|---|
| Valid body | 200 / 201 |
Empty body ({}) | 400 |
| Missing required field | 400 with field name |
Wrong type (age: "thirty" for an integer field) | 400 or 422 |
| Extra unknown fields | usually accepted/ignored, sometimes 400 |
| Malformed JSON (trailing comma, unquoted keys) | 400 |
Wrong Content-Type (sending JSON as text/plain) | 415 Unsupported Media Type |
| Body that exceeds size limit | 413 Payload Too Large |
| Nested object missing required sub-field | 400 with path to the field |
null for a required field | 400 |
Two traps worth flagging:
- Empty body: some APIs accept
{}as "use all defaults"; others reject it. Confirm and test. - Extra fields: APIs that silently accept unknown fields are easier to evolve but harder to spot typos in. APIs that reject them surface client mistakes early. Either is defensible — test for the team's choice.
Combining all four
A real-world example that uses path, query, body, and headers:
PATCH /api/users/123/notifications?markRead=true HTTP/1.1
Authorization: Bearer eyJhbGciOiJI...
Content-Type: application/json
X-Request-Id: test-9001
{"channels": ["email"], "frequency": "daily"}Tests should mix-and-match:
- Valid path + invalid query (
?markRead=maybe) → 400. - Valid query + invalid body (missing
channels) → 400. - Wrong path id (no such user) + valid body → 404 (the body shouldn't even be parsed).
- Missing auth header + everything else valid → 401.
The order of checks usually goes: auth → path → body → query. A 401 should come before a 400 if both are wrong.
The corner cases that bite
A handful of scenarios catch real bugs in real APIs:
- Trailing slashes —
/users/123vs/users/123/. Some APIs treat them as different endpoints; some redirect; some return inconsistent behaviour. Test once. - Path traversal —
/users/..%2Fadmin. Should never affect routing. - Query keys differing only by case —
?Page=1vs?page=1. Should be handled consistently. - Body with both JSON and form-style content —
?name=Alicein URL plus{"name": "Bob"}in body. Which wins? Document and test.
⚠️ Common mistakes
- Mixing up where parameters belong. Filters in the path (
/users/admin/list) instead of the query (/users?role=admin) is a classic. URL semantics break, caching breaks, security review fails. - Hand-building URLs by string concatenation.
f"{base}/users?name={user_name}"breaks the momentuser_namecontains&. Use your client's URL builder orurlencode. - Forgetting to set
Content-Typeon POST/PUT/PATCH. Some servers default toapplication/octet-streamand refuse to parse the body. Always set it explicitly.
🎯 Practice task
Map a real endpoint's parameters. 25 minutes.
- Pick any documented API endpoint with multiple parameter types —
GET /repos/{owner}/{repo}/issues?state=closedon GitHub is a good one. - Identify each parameter and where it lives: path, query, header, or body.
- For each, write a one-sentence test for: valid value, missing/empty, invalid type, edge boundary.
- Run three of those tests with curl. Note actual responses vs your expectations.
- Try a combined failure: invalid path id AND missing required query param. Which error wins? Is the priority documented?
- Stretch: try sending the same request with the parameters in deliberately wrong places (filters in path, ids in query). Note how the API responds — predictable failure or unpredictable?
You can now reason about every part of an HTTP request. The next lesson handles the one body type that doesn't fit JSON: file uploads.