Skip to content

Pagination

Every list endpoint is cursor-paginated.

http
GET /api/public/v1/transactions?limit=100 HTTP/1.1
Authorization: Bearer tsk_live_dP4y…
json
{
  "data": [ { "id": "trx_01J9…" }, { "id": "trx_01J9…" } ],
  "next_cursor": "eyJjcmVhdGVkX2F0Ijoi…"
}
  • Pass next_cursor back as ?cursor= to get the following page.
  • next_cursor: null means you are done. That is the loop's only exit condition — do not stop on a short page, because a page shorter than your limit is legal.
  • limit is capped at the maximum page size shown in Rate limits and quotas. An over-limit value is clamped, not rejected, and the response states the effective limit — asking for 10,000 gets you a page, not a 400.
  • Treat a cursor as opaque. It encodes a sort position, its format is not part of the contract, and constructing one yourself is unsupported.

Why there is no ?page=2

Offset pagination skips rows when the underlying list grows while you are reading it — which is exactly what a transaction log does. Page 1 is fetched, three scans happen, page 2 starts three rows late, and three transactions are silently never synced. Nothing errors; your totals are just wrong.

A cursor encodes a stable position (created_at, id), so a concurrent write cannot shift the window under you. It costs you a random-access page number you would not have used anyway.

Syncing a growing log

js
let cursor = null;
do {
  const url = new URL('https://api.tesserapp.eu/api/public/v1/transactions');
  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(`HTTP ${res.status}`);
  const page = await res.json();

  for (const row of page.data) await handle(row);   // idempotent on row.id
  cursor = page.next_cursor;
} while (cursor);

Make handle() idempotent on the row id and a re-run costs nothing but time.

For an ongoing sync, prefer webhooks over polling: they arrive in seconds, they carry the same identity fields, and they do not spend your rate limit. Poll to backfill, subscribe to keep up.

Filtering

List endpoints filter server-side — transactions by location, program, card, type and date range; cards by external_ref, holder_email, program_id and status. Filter there rather than paging the whole log and filtering locally: it is faster, and it does not spend your rate limit on rows you throw away.

Requires the Business API add-on.