beginner·7 min read·Updated May 1, 2026

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:

  1. Pagination — how to split a large list into pages.
  2. Sorting — which order to return results in.
  3. Filtering — which subset of results matches criteria.
  4. 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:

POST/api/v1/products
Seed the sandbox with a product so pagination has something to show.
curl -X POST 'https://demo.totalshiftleft.ai/api/v1/products' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Product A","price":29.99,"category":"electronics"}'
GET/api/v1/products?page=1&limit=5&sort=price&order=desc
Paginate, sort, and inspect the pagination metadata.
curl -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:

GET/api/v1/products?category=electronics
Filter by any field — case-insensitive substring match.
curl -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.

Related lessons

Read more on the blog