Query Parameters, Path Parameters, and Request Body

8 min read

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:

ScenarioExpected
Valid existing id200 with that resource
Valid format, doesn't exist404 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 access403 (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:

ScenarioExpected
Valid valuefiltered/paginated result
Missing optional paramsensible default (page=1, limit=20)
Missing required param400
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

ScenarioExpected
Valid body200 / 201
Empty body ({})400
Missing required field400 with field name
Wrong type (age: "thirty" for an integer field)400 or 422
Extra unknown fieldsusually 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 limit413 Payload Too Large
Nested object missing required sub-field400 with path to the field
null for a required field400

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/123 vs /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=1 vs ?page=1. Should be handled consistently.
  • Body with both JSON and form-style content?name=Alice in 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 moment user_name contains &. Use your client's URL builder or urlencode.
  • Forgetting to set Content-Type on POST/PUT/PATCH. Some servers default to application/octet-stream and refuse to parse the body. Always set it explicitly.

🎯 Practice task

Map a real endpoint's parameters. 25 minutes.

  1. Pick any documented API endpoint with multiple parameter types — GET /repos/{owner}/{repo}/issues?state=closed on GitHub is a good one.
  2. Identify each parameter and where it lives: path, query, header, or body.
  3. For each, write a one-sentence test for: valid value, missing/empty, invalid type, edge boundary.
  4. Run three of those tests with curl. Note actual responses vs your expectations.
  5. Try a combined failure: invalid path id AND missing required query param. Which error wins? Is the priority documented?
  6. 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.

// tip to track lessons you complete and pick up where you left off across devices.