Concepts
Pagination

Pagination

Lists page in one of two ways. Each endpoint's parameters in the REST API reference say which it takes.

Cursor paging

Send limit, then pass the previous answer's nextCursor as cursor until hasMore is false (or nextCursor is null).

ListPage size (default / max)
GET /events20 / 100
GET /products (also takes offset)20 / 100
GET /me/directory24 / 100
GET /me/friends50 / 100
GET /me/members/{usertag}/friends24 / 100
GET /me/notifications20 / 100
GET /finance/entries, GET /finance/payouts100 / 500 (a larger value is clamped, not refused)
let cursor: string | undefined;
do {
  const page = await cobuntu.events.list({ upcoming: true, limit: 50, cursor });
  render(page.events);
  cursor = page.nextCursor ?? undefined;
} while (cursor);
  • Treat the cursor as opaque. Do not build one from an id.
  • Keep the same filters and sort across pages.
  • A malformed cursor is a 400, deliberately: restarting silently from the top would look like it worked and duplicate everything downstream.
  • Finance entries come back oldest first, so a cursor walks forward in time and an incremental sync never re-reads the head of the list. Every entry also carries its own cursor to resume from.

Offset paging

Send limit and offset. Answers carry a pagination object { total, limit, offset, hasMore } where the endpoint reports one.

ListPage size (default / max)
GET /memberssee the reference
GET /articles, GET /coursessee the reference
GET /me/purchases20 / 50
GET /me/ledger20 / 100
GET /api/v1/users/me/products, GET /api/v1/users/me/events20 / 100

Filters apply before paging

Filters are applied before the page is cut, so every page is full. An unknown category id narrows to nothing and returns an empty page rather than a 400, so a filter pointed at a deleted category does not break the page.