Pagination
Every list endpoint in Patomic uses the same pagination contract. Once you’ve integrated against one endpoint, you’ve integrated against all of them.
Wire format
Section titled “Wire format”GET /v1/keys?page_size=50&cursor=Mzs.7e4a31f2c…Response:
{ "data": [ /* up to page_size rows */ ], "next_cursor": "Mzs.b8e91a7f4…" /* or null when no more */}page_size— integer, 1–200. Defaults to 50. Validated server-side.cursor— opaque, signed by Patomic. Pass backnext_cursorfrom the previous response verbatim.next_cursor— the value to send on the next request.nullmeans you’ve reached the end.
Why cursor (keyset), not offset
Section titled “Why cursor (keyset), not offset”- Stable under concurrent writes. Offset-based pagination duplicates or skips rows when new entries appear between page 1 and page 2 fetches. Cursors don’t have this bug.
- O(1) per page. Offset gets linearly slower because the database scans-and-discards. Cursors hit an index directly.
Walking a list
Section titled “Walking a list”async function listAll<T>(path: string): Promise<T[]> { const out: T[] = []; let cursor: string | null = null;
do { const url = new URL(`https://patomic-api.pedrotengelmann.workers.dev${path}`); url.searchParams.set("page_size", "200"); if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PATOMIC_KEY}` }, }); const body = (await res.json()) as { data: T[]; next_cursor: string | null }; out.push(...body.data); cursor = body.next_cursor; } while (cursor);
return out;}Cursor properties
Section titled “Cursor properties”- Tamper-resistant. Each cursor is HMAC-signed with Patomic’s server secret. A cursor altered by even one character is rejected with
invalid_parameter,param: "cursor". - Versioned. A
vfield in the encoded payload lets us roll the wire format if needed without misinterpreting old cursors. - Not bound to a user. A cursor minted in one user’s session and sent in another’s request is harmless — Patomic’s per-user
WHEREfilter still scopes results. (This is a tested invariant.) - Disposable. Cursors point at “the row after this one”. They have no meaning outside that traversal. Don’t persist them long-term.