Path Parameters vs Query Parameters, Pagination, Filtering and Sorting
Open any shopping app, search “headphones”, filter by price, sort by rating and scroll to page 3. Every one of those taps changes the parameters of an API call. Parameters are where a huge share of API bugs hide: off-by-one pagination, filters that ignore case, sorting that silently fails, and ids that let you read someone else’s data. This lesson explains path vs query parameters and gives you a complete test design for pagination, filtering, sorting and search, all runnable on the practice API.
/users/{id} → /users/1. A query parameter comes after the ? as key=value pairs joined by & and is optional; it filters, sorts, searches or paginates a collection, for example /users?role=admin&page=2. Use path parameters for which resource and query parameters for how to view a collection.Path vs Query Parameters (with a Library Example)
/books/4521. “Show me books in the Testing section, written after 2020, sorted by title, 10 per shelf” describes how to browse many books: query parameters, /books?section=testing&year_gte=2020&sort=title&per_page=10.| Path parameter | Query parameter | |
|---|---|---|
| Position | Inside the path | After ?, separated by & |
| Example | /users/1/orders | /users?city=Pune&active=true |
| Required? | Yes, part of the route | Usually optional, with defaults |
| Purpose | Identify a resource | Filter, sort, search, paginate, select fields |
| Wrong value | Typically 404 (or 400 if wrong type) | Typically 400 (validation) |
| Order matters? | Yes | No |
There are two more places for data: headers (metadata such as auth) and the request body (the payload for POST/PUT/PATCH). Some older APIs also use matrix parameters (;color=red), but you’ll rarely see them.
Testing Path Parameters
Path parameters look simple, but they carry some of the worst bugs, including the number one API security risk.
| Test | Request | Expected |
|---|---|---|
| Valid id | GET /users/1 | 200, user 1 |
| Non-existent id | GET /users/999 | 404 with message |
| Wrong type | GET /users/abc | 400 |
| Boundary zero | GET /users/0 | 400 |
| Negative / decimal | /users/-1, /users/1.5 | 400 |
| Huge number | /users/99999999999999999999 | 400 or 404, never 500 |
| Special chars / injection | /users/1'%20OR%201=1 | 400, no SQL error |
| Another user’s id (BOLA) | User A’s token on /orders/{B's order} | 403 or 404 |
| Nested resource | GET /users/1/orders | Only orders with user_id = 1 |
https://sqatools.in/wp-json/sqa-api/v1/users/0curl -i \
"https://sqatools.in/wp-json/sqa-api/v1/users/0"Pagination: Why APIs Split Data into Pages
Amazon can’t return 50 million products in one response. Pagination returns a small slice at a time. The three common styles:
| Style | Example | Pros / cons | Used by |
|---|---|---|---|
| Page-based | ?page=2&per_page=5 | Simple; can skip or repeat items if data changes | Admin panels, the practice API |
| Offset/limit | ?offset=5&limit=5 | Flexible; slow for large offsets | Many SQL-backed APIs |
| Cursor-based | ?cursor=eyJpZCI6MTB9&limit=5 | Stable and fast; can’t jump to page 7 | Instagram, Twitter/X feeds, Stripe |
The practice API has 12 users and uses page-based pagination. It returns the page details in both the body (meta, links) and headers (X-Total-Count, X-Total-Pages, Link).
https://sqatools.in/wp-json/sqa-api/v1/users?page=2&per_page=5curl -i \
"https://sqatools.in/wp-json/sqa-api/v1/users?page=2&per_page=5"Page 2 with 5 per page must start at the 6th record (id 6) and contain ids 6 to 10.
Pagination Test Cases (Boundary Value Analysis)
| # | Scenario | Request | Expected |
|---|---|---|---|
| 1 | Default page size | /users | 5 items, page 1 |
| 2 | Middle page | ?page=2&per_page=5 | ids 6-10 |
| 3 | Last page (partial) | ?page=3&per_page=5 | 2 items (ids 11-12) |
| 4 | Beyond last page | ?page=10 | 200 with empty list (not an error) |
| 5 | Page 0 | ?page=0 | 400 |
| 6 | Max page size | ?per_page=50 | 200 |
| 7 | Max + 1 | ?per_page=51 | 400 |
| 8 | Non-numeric | ?page=two | 400 |
| 9 | No duplicates / no gaps | Walk all pages | 12 unique ids in total |
| 10 | Metadata correct | any page | total=12, total_pages = ceil(12 / per_page) |
| 11 | Links | ?per_page=5 | Link has next/last; prev null on page 1 |
Filtering
Filters narrow a collection. Test each filter alone, in combination, with invalid values and with edge cases such as case sensitivity.
| Scenario | Request | Expected |
|---|---|---|
| Single filter | /users?role=admin | Every result has role admin |
| Case-insensitive | /users?city=pune | Matches “Pune” |
| Boolean | /users?active=false | Only inactive users |
| Invalid boolean | /users?active=maybe | 400 |
| Combined (AND) | /users?role=admin&city=Chennai | Both conditions true |
| Range | /products?min_price=1000&max_price=5000 | All prices in range (inclusive?) |
| Invalid range | ?min_price=5000&max_price=100 | 400 “min_price cannot be greater than max_price” |
| Enum validation | /products?category=toys | 400 (unknown category) |
| Unknown parameter | /users?colour=red | Documented behaviour: ignored or 400 |
https://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100curl -i \
"https://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100"Sorting
| Scenario | Request | Expected |
|---|---|---|
| Sort descending | /users?sort=age&order=desc | Ages in non-increasing order |
| Default sort | /users | By id ascending (as documented in meta.sort) |
| Unknown sort field | /users?sort=salary | 400 (only whitelisted fields) |
| Invalid order | /users?order=up | 400 |
| Sort + pagination | ?sort=age&order=desc&page=2 | Page 2 continues the sorted order |
| Ties | Equal ages | Stable secondary order (e.g. id) |
Why the whitelist matters: if sort is pasted directly into SQL (ORDER BY {sort}), it is an injection point. A 400 for unknown fields is the secure behaviour.
Search
- Match:
/users?q=guptareturns Manish Gupta. - No match:
/users?q=zzzreturns200with an empty list, not 404. - Partial and case: “GUP”, “gup” behave as documented.
- Special characters:
%,_,', emojis, Hindi text (?q=%E0%A4%86%E0%A4%B6%E0%A4%BE) don’t cause 500. - Long input: 5,000 characters is rejected cleanly.
- Search + filter + pagination together.
URL Encoding: The Silent Bug Source
Query values containing spaces or special characters must be URL-encoded: a space becomes %20 (or +), & becomes %26, @ becomes %40.
?q=Tom & Jerry. The server read q=Tom plus an empty parameter named Jerry. Always test values containing &, =, +, # and spaces.Postman and Playwright’s params option encode values for you; hand-built URL strings don’t.
Automating Parameter Tests with Playwright
import { test, expect } from '@playwright/test';
const BASE = 'https://sqatools.in/wp-json/sqa-api/v1';
test('page 2 starts at the 6th record', async ({ request }) => {
const res = await request.get(`${BASE}/users`, { params: { page: 2, per_page: 5 } });
const body = await res.json();
expect(body.data.map((u: any) => u.id)).toEqual([6, 7, 8, 9, 10]);
expect(body.meta).toMatchObject({ page: 2, per_page: 5, total: 12, total_pages: 3 });
});
test('walking all pages returns 12 unique users', async ({ request }) => {
const ids: number[] = [];
for (let page = 1; ; page++) {
const body = await (await request.get(`${BASE}/users`, { params: { page, per_page: 5 } })).json();
if (body.data.length === 0) break;
ids.push(...body.data.map((u: any) => u.id));
}
expect(ids).toHaveLength(12);
expect(new Set(ids).size).toBe(12); // no duplicates
});
test('every filtered user is an admin', async ({ request }) => {
const body = await (await request.get(`${BASE}/users`, { params: { role: 'admin', per_page: 50 } })).json();
expect(body.data.length).toBeGreaterThan(0);
for (const u of body.data) expect(u.role).toBe('admin');
});
test('sorted by age descending', async ({ request }) => {
const body = await (await request.get(`${BASE}/users`, { params: { sort: 'age', order: 'desc', per_page: 50 } })).json();
const ages = body.data.map((u: any) => u.age);
expect(ages).toEqual([...ages].sort((a, b) => b - a));
});
for (const bad of ['per_page=51', 'page=0', 'active=maybe', 'sort=salary', 'order=up']) {
test(`invalid query ${bad} -> 400`, async ({ request }) => {
expect((await request.get(`${BASE}/users?${bad}`)).status()).toBe(400);
});
}
Interview Questions: Parameters and Pagination
What is the difference between path parameters and query parameters?
Path parameters are part of the URL path and identify a specific resource, such as /users/1; they are required. Query parameters follow the question mark as key=value pairs and are usually optional; they filter, sort, search or paginate a collection, such as /users?role=admin&page=2.
How do you test pagination?
Check the default page size, first, middle and last pages, a page beyond the last (empty list), page 0 and negative pages, maximum and maximum+1 page size, non-numeric values, metadata such as total and total_pages, navigation links, no duplicates or gaps when walking all pages, and behaviour when data changes during paging.
What is the difference between offset and cursor pagination?
Offset pagination uses a numeric offset or page number; it is simple and allows jumping to any page but can skip or repeat items when data changes and gets slow for large offsets. Cursor pagination uses an opaque pointer to the last item; it is stable and fast but only supports next/previous navigation.
What should an API return for a page beyond the last page?
Typically 200 OK with an empty data array and correct metadata. Some APIs return 404, which is acceptable only if documented. A 500 is a bug.
Why should sort fields be whitelisted?
If sort values are inserted directly into database queries, they can be used for SQL injection or to sort by sensitive hidden columns. Only documented fields should be accepted; others should get 400.
What is BOLA and how do path parameters relate to it?
Broken Object Level Authorization (also called IDOR) happens when changing an id in the path, such as /orders/1001 to /orders/1002, returns another user’s data. Testers must verify that each id is checked against the logged-in user’s permissions.
What is URL encoding and why does it matter?
URL encoding converts special characters into percent codes, such as a space into %20 and & into %26, so they are not misread as URL syntax. Unencoded values can split parameters or break routing.
Should a search with no results return 404?
No. The collection exists; it is just empty for this query. The correct response is 200 with an empty list.
How do you test filters thoroughly?
Test each filter alone and combined, valid and invalid values, case sensitivity, boundaries for ranges, cross-field validation such as min greater than max, unknown parameters, and assert every returned item, not just the first one.
Where would you send a large amount of data: query parameters or the body?
In the request body. URLs have practical length limits (often around 2,000 to 8,000 characters) and are logged by servers and proxies, so large or sensitive data should not be placed in query parameters.