Pagination
Offset pagination for anything a human reads, cursor pagination for anything that walks every row — and why the two behave differently on purpose.
Krrim has two pagination modes, for two genuinely different jobs.
Offset — for rendering a page
The default. ?limit= and ?offset=, in DRF's LimitOffset style:
GET /api/v1/projects/APOLLO/tasks/?limit=50&offset=100{
"success": true,
"data": [ ],
"pagination": { "count": 214, "limit": 50, "offset": 100 }
}The default limit is 50 and the maximum is 200. A request with no
limit is capped rather than dumping the table. ?page= and ?page_size=
are accepted as aliases.
Offset mode honours your ?ordering=, because it exists to render a list for a
person and presentation order is the whole point.
Cursor — for walking every row
Offset pagination silently skips and repeats rows on a live board. Delete a task while a walk is halfway through and every later page shifts back by one, so some task is never returned. Create one and a task comes back twice. Nothing errors — you simply end up with a wrong picture and no way to detect it. If you are syncing, use a cursor.
Pass ?cursor= (empty value starts from the beginning) to switch a list into
cursor mode:
GET /api/v1/projects/APOLLO/tasks/?cursor=&limit=200{
"success": true,
"data": [ ],
"pagination": { "limit": 200, "next_cursor": "1841", "has_more": true }
}Keep passing the returned next_cursor until has_more is false.
Cursor mode ignores your ordering, deliberately
A stable cursor needs a total order over a column that never changes. Sort fields change constantly — a task is reprioritised, reassigned, renamed — and a cursor over a mutable ordering has exactly the skip-and-repeat problem it was meant to solve.
So cursor mode orders by primary key, ascending, and drops your ?ordering=.
That is not a display order and is not meant to be: the audience is a client
that wants every row exactly once and does not care what order they arrive in.
It still does not carry deletions
A cursor walk returns everything that exists. It cannot tell you what stopped existing, because a list of tasks has no way to mention a task that is gone.
For incremental sync — where "this task was deleted" is information you need — use the changes feed instead.