Path Parameters vs Query Parameters, Pagination, Filtering and Sorting

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.

Quick answerA path parameter is part of the URL path and identifies a specific resource, for example /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.
Advertisement

Path vs Query Parameters (with a Library Example)

Real-life example: a library “Give me book number 4521” points to one exact book: a path parameter, /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 parameterQuery parameter
PositionInside the pathAfter ?, separated by &
Example/users/1/orders/users?city=Pune&active=true
Required?Yes, part of the routeUsually optional, with defaults
PurposeIdentify a resourceFilter, sort, search, paginate, select fields
Wrong valueTypically 404 (or 400 if wrong type)Typically 400 (validation)
Order matters?YesNo

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.

TestRequestExpected
Valid idGET /users/1200, user 1
Non-existent idGET /users/999404 with message
Wrong typeGET /users/abc400
Boundary zeroGET /users/0400
Negative / decimal/users/-1, /users/1.5400
Huge number/users/99999999999999999999400 or 404, never 500
Special chars / injection/users/1'%20OR%201=1400, no SQL error
Another user’s id (BOLA)User A’s token on /orders/{B's order}403 or 404
Nested resourceGET /users/1/ordersOnly orders with user_id = 1
GEThttps://sqatools.in/wp-json/sqa-api/v1/users/0
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/users/0"
Advertisement

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:

StyleExamplePros / consUsed by
Page-based?page=2&per_page=5Simple; can skip or repeat items if data changesAdmin panels, the practice API
Offset/limit?offset=5&limit=5Flexible; slow for large offsetsMany SQL-backed APIs
Cursor-based?cursor=eyJpZCI6MTB9&limit=5Stable and fast; can’t jump to page 7Instagram, 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).

GEThttps://sqatools.in/wp-json/sqa-api/v1/users?page=2&per_page=5
curl -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)

#ScenarioRequestExpected
1Default page size/users5 items, page 1
2Middle page?page=2&per_page=5ids 6-10
3Last page (partial)?page=3&per_page=52 items (ids 11-12)
4Beyond last page?page=10200 with empty list (not an error)
5Page 0?page=0400
6Max page size?per_page=50200
7Max + 1?per_page=51400
8Non-numeric?page=two400
9No duplicates / no gapsWalk all pages12 unique ids in total
10Metadata correctany pagetotal=12, total_pages = ceil(12 / per_page)
11Links?per_page=5Link has next/last; prev null on page 1
Real bug: the missing order An e-commerce admin exported orders page by page. Because new orders arrived during the export and the list was sorted newest first, every new order pushed one row to the next page, and one order per page was skipped. Reconciliation found Rs 4 lakh of “missing” orders. Cursor pagination fixed it. Test: insert data while paginating.
Advertisement

Filtering

Filters narrow a collection. Test each filter alone, in combination, with invalid values and with edge cases such as case sensitivity.

ScenarioRequestExpected
Single filter/users?role=adminEvery result has role admin
Case-insensitive/users?city=puneMatches “Pune”
Boolean/users?active=falseOnly inactive users
Invalid boolean/users?active=maybe400
Combined (AND)/users?role=admin&city=ChennaiBoth conditions true
Range/products?min_price=1000&max_price=5000All prices in range (inclusive?)
Invalid range?min_price=5000&max_price=100400 “min_price cannot be greater than max_price”
Enum validation/products?category=toys400 (unknown category)
Unknown parameter/users?colour=redDocumented behaviour: ignored or 400
GEThttps://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100"
Always assert every item Checking only the first result of a filter is a common mistake. Loop through all returned items: one wrong record on page 3 is still a bug.
Advertisement

Sorting

ScenarioRequestExpected
Sort descending/users?sort=age&order=descAges in non-increasing order
Default sort/usersBy id ascending (as documented in meta.sort)
Unknown sort field/users?sort=salary400 (only whitelisted fields)
Invalid order/users?order=up400
Sort + pagination?sort=age&order=desc&page=2Page 2 continues the sorted order
TiesEqual agesStable 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.

Advertisement
  • Match: /users?q=gupta returns Manish Gupta.
  • No match: /users?q=zzz returns 200 with 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.

Real bug A search for “Tom & Jerry” was sent unencoded as ?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.

Advertisement

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);
  });
}
Advertisement

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.

Advertisement
View the full API testing syllabus