Pagination
On this page
Why pagination is mandatory
Unpaginated collection endpoints create performance problems, timeouts, and huge memory usage. Pagination protects both your API and your clients.
Offset pagination
Offset pagination uses limit and offset. It is easy but can be slow at large offsets and can produce inconsistent results when data changes.
GET /api/orders?limit=50&offset=0 GET /api/orders?limit=50&offset=50
Cursor pagination
Cursor pagination uses an opaque cursor pointing to a position in a stable ordering. It is faster and more consistent for large datasets.
GET /api/orders?limit=50&cursor=eyJpZCI6OTg3fQ==
Stable ordering is critical
Pagination must use a deterministic sort order (e.g., createdAt + id). Without stable ordering, clients will see duplicates or missing items across pages.
Response shape (recommended)
Return items plus paging metadata (nextCursor or total for offset pagination).
Example: cursor response
{
"items": [ { "id": 1 }, { "id": 2 } ],
"nextCursor": "eyJpZCI6Mn0="
}
Limits and protection
- Enforce a max limit (e.g., 100).
- Reject insane limits (400) or clamp them server-side.
- Document default limit behavior.
Common mistakes
- Returning all records “because it's easier”
- Using offset pagination with frequently-changing datasets
- No stable ordering, causing duplicates across pages
Checklist
- All list endpoints are paginated.
- Ordering is stable and documented.
- Max limit exists and is enforced.