Query Parameters, Pagination, Sorting & Filtering
Every useful API returns lists of things. Here's how to page through, sort, and filter them — with runnable examples.
Why query parameters matter
Without query parameters, GET /users would return every user in your database. That's fine for 10 users and a disaster for 10 million. Query parameters let clients ask for a slice of the data they actually need.
Four things APIs typically support via query parameters:
- Pagination — how to split a large list into pages.
- Sorting — which order to return results in.
- Filtering — which subset of results matches criteria.
- Field selection — which fields to include (less common in REST, central in GraphQL).
Pagination: offset vs cursor
Two dominant pagination styles.
Offset/limit (page/limit) pagination: the client says "give me page 3, 20 items per page" and the server skips the first 40 items.
GET /users?page=3&limit=20
GET /users?offset=40&limit=20
Simple to implement, easy to test, works for human UIs. Drawback: at huge scale, OFFSET 10000 is slow because the database still scans through 10,000 rows. And if a user is inserted at position 5 while you're reading page 3, you might see the same item twice or miss it.
Cursor pagination: the server returns a next_cursor token, and the client sends it back to get the next page.
GET /users?limit=20
→ { items: [...], next_cursor: "eyJpZCI6MTIz..." }
GET /users?limit=20&cursor=eyJpZCI6MTIz...
Stable under inserts, fast at any scale, but can't jump to "page 7." Used by Twitter, GitHub, Stripe, and pretty much any API with serious scale.
Our sandbox uses the simpler page/limit style for clarity. Seed a product first, then try pagination:
/api/v1/productscurl -X POST 'https://demo.totalshiftleft.ai/api/v1/products' \
-H 'Content-Type: application/json' \
-d '{"name":"Product A","price":29.99,"category":"electronics"}'/api/v1/products?page=1&limit=5&sort=price&order=desccurl -X GET 'https://demo.totalshiftleft.ai/api/v1/products?page=1&limit=5&sort=price&order=desc'Look at the pagination object in the response:
{
"total": 12,
"page": 1,
"limit": 5,
"pages": 3,
"hasNext": true,
"hasPrev": false
}
Good APIs always include pagination metadata. Without it, the client doesn't know whether there's a next page without asking.
Sorting
Most APIs accept sort (field) and order (asc or desc):
GET /products?sort=price&order=desc
Some APIs combine them into one field with a prefix:
GET /products?sort=-price # minus = descending
GET /products?sort=price,-name # multi-field, price asc then name desc
Both are fine. What matters is consistency: if sort accepts price, it must also accept any other sortable field documented in the spec. When you test a list endpoint, test sort on at least two fields, in both directions.
Filtering
The simplest form: one query param per field, with equals-style matching.
GET /products?category=electronics
GET /products?category=electronics&price_min=100&price_max=500
Our sandbox supports case-insensitive substring matches on any field:
/api/v1/products?category=electronicscurl -X GET 'https://demo.totalshiftleft.ai/api/v1/products?category=electronics'More advanced APIs support operators:
GET /products?price[gte]=100&price[lt]=500
GET /products?created_at[gte]=2026-01-01
And some support a full query language:
GET /products?q=price>100 AND category=electronics
The trade-off: richer filtering = richer test surface. Every filter combination is a potential bug.
Common mistakes
1. No maximum limit. If GET /users?limit=1000000 actually returns a million users, your API is a DOS target. Cap limits server-side (typically 100).
2. Ambiguous empty pages. Does page=99 on a dataset with 3 pages return 200 [], 200 with pagination.total=0, or 404? Pick one and document it. Most APIs return an empty list with pagination metadata.
3. Non-deterministic default sorting. If you don't explicitly sort, databases don't guarantee any order. Two calls to the same endpoint can return different orders. Always set a default sort (usually created_at desc).
4. Filters that silently ignore bad fields. If GET /users?xyz=123 silently returns all users because xyz doesn't exist, that's a bug — the client probably meant something. Either 400 or ignore with a warning header.
What to test
For any paginated list endpoint, your test matrix should include:
- First page, default params
- Last page (check
hasNext: false) - Out-of-range page (
page=9999) - Zero limit (
limit=0) and negative limit — should 400 - Limit over max — should either clamp or 400
- Sort on a valid field, both directions
- Sort on an invalid field — document the behavior and assert it
- Filter that matches
- Filter that matches nothing (empty list is correct, not 404)
- Multiple combined filters
- Invalid filter values (
price=abc) — should 400
This looks like a lot, and it is. Which is why we generate these tests from the OpenAPI spec automatically in ShiftLeft.
What's next
You've covered the four pillars of API fundamentals: what an API is, methods, status codes, and the request/response anatomy. Next stop: the REST cluster to dig into REST specifically, or authentication if you're ready to deal with protected endpoints.
Frequently asked questions
What is the difference between offset and cursor pagination?+
Offset pagination asks for a page number or offset plus a limit, which is simple but slows down at large offsets and can skip or repeat items during inserts. Cursor pagination returns a next_cursor token to send back, which is stable and fast but cannot jump to a specific page.
What should you test on a paginated API endpoint?+
Test the first page with default parameters, the last page, an out-of-range page, zero and negative limits, a limit over the maximum, sorting on valid and invalid fields in both directions, matching and non-matching filters, combined filters, and invalid filter values.
Should an out-of-range page return 404 or an empty list?+
Either can work as long as the API picks one and documents it, but most APIs return an empty list with pagination metadata. A filter that matches nothing should also return an empty list, not a 404.
Related lessons
Every HTTP request has the same parts. Once you know them, every API you'll ever test becomes readable.
GET, POST, PUT, PATCH, DELETE — the five verbs that carry 99% of API traffic — plus QUERY, the emerging method for safe reads with a body. Here's what each one means, with runnable examples.
An API is how two pieces of software talk to each other. Here's what that actually means — with runnable examples.