Skip to content

Pagination

Collections are paged with an opaque cursor. Ask for a page, and if there is another the response tells you how to get it.

Last updated

How to page

Examplebash
curl 'https://api.cmskite.com/v1/blog/posts?limit=25' -H "authorization: Bearer $KEY"
# → "pagination": { "hasNext": true, "nextCursor": "eyJ2IjoxLCJzIjoi…", "limit": 25 }

curl 'https://api.cmskite.com/v1/blog/posts?limit=25&cursor=eyJ2IjoxLCJzIjoi…' \
  -H "authorization: Bearer $KEY"
All of it, in a loopts
async function* everyPost(key: string) {
  let cursor: string | null = null
  do {
    const url = new URL('https://api.cmskite.com/v1/blog/posts')
    url.searchParams.set('limit', '100')
    if (cursor) url.searchParams.set('cursor', cursor)

    const res = await fetch(url, { headers: { authorization: `Bearer ${key}` } })
    if (!res.ok) throw new Error(`CMSKite: ${res.status}`)
    const page = await res.json()

    yield* page.data
    cursor = page.pagination.nextCursor
  } while (cursor)
}
ParameterDefaultMaxNotes
limit25100Outside the range is a 400, not a silent clamp
cursorOpaque. Pass back exactly what you were given

Why there is no ?page=

OFFSET makes the database count and discard every row it skips. On a table with ten million rows, ?page=5000 is a ten-second query that any caller can issue at any time, and there is no fix for it that keeps the parameter.

It is also wrong in a way people do not notice: if something is created while you are paging, every later page shifts by one and you silently see a row twice or miss one entirely. A cursor points at a position in the ordering rather than at a count, so it is not affected by what arrives after you started.

What a cursor is

Base64url of a small versioned object: the sort value of the last row you saw, and its id as a tiebreaker. The version is in there from the first release, because a cursor is a public contract — a cursor from a future encoding is refused rather than misread.

The id tiebreaker is not decoration. Two posts published in the same millisecond with only a timestamp in the ordering come back in an unstable order, and a page boundary then skips one and repeats the other. Every sort carries the tiebreaker, and every timestamp a cursor is built from is stored at exactly the precision a cursor can carry.

Sorting

Sorts are a closed list, mapped to columns with indexes behind them. Anything else is a 400 UNSUPPORTED_SORT rather than a sequential scan a customer discovers as a slow endpoint.

ValueOrders by
-publishedAtNewest published first — the default
publishedAtOldest published first
-createdAt / createdAtWhen the row was made
-updatedAt / updatedAtWhen it last changed
title / -titleAlphabetical