Appearance
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_cursorback as?cursor=to get the following page. next_cursor: nullmeans you are done. That is the loop's only exit condition — do not stop on a short page, because a page shorter than yourlimitis legal.limitis 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.