Pagination and syncing
Every list answers the same shape:
{ "data": [], "pagination": { "total": 128, "limit": 20, "offset": 0, "hasMore": true }}limit defaults to 20 and caps at 100.
Without a sync parameter a list is ordered newest first (createdAt descending); with
one it is ordered by updatedAt ascending. Every order carries the row id as a tiebreak,
so no two rows ever compare equal.
Browsing: limit and offset
Section titled “Browsing: limit and offset”Good for showing a page to a person:
curl -s "$TKANA_API/customers?limit=20&offset=40" -H "Authorization: Bearer $TKANA_KEY"Rows move between pages while data changes, so offset paging is fine for a screen and wrong for a copy you intend to keep.
Syncing: updatedAfter, then the cursor
Section titled “Syncing: updatedAfter, then the cursor”To hold a current copy, ask for what changed and follow the cursor.
-
Start from your watermark.
Terminal window curl -s "$TKANA_API/tickets?updatedAfter=2026-03-01T00:00:00Z" \-H "Authorization: Bearer $TKANA_KEY" -
Take
pagination.nextCursorfrom the response and pass it back ascursor.Terminal window curl -s "$TKANA_API/tickets?cursor=eyJ1cGRhdGVkQXQiOi..." \-H "Authorization: Bearer $TKANA_KEY" -
Repeat while
hasMoreis true. Store the lastnextCursorand resume from it next time — it stays valid once you are caught up.
Removals arrive in the sync
Section titled “Removals arrive in the sync”A sync reports rows that were created, changed, or removed. A removal arrives in
data in place of the row, in its own position in the same ordering, so one cursor
carries both streams and there is nothing to reconcile separately:
{ "object": "removed", "id": "8f14e45f-ceea-4c1a-9b31-9f3ba3a5d9c1", "resource": "ticket", "removedAt": "2026-05-04T09:12:33.019Z"}Branch on object === "removed" before reading any other field. No published resource
carries that field, so the two shapes can never be confused — and a client that checks it
first needs no other change when a future resource starts reporting removals too.
Three things worth knowing:
- Only a sync contains them. An ordinary list — no
updatedAfter, nocursor— returns what exists, and never a removal. - A record is reported removed once. Removals recorded before your first sync are not replayed, so treat a removal for something you do not hold as a no-op.
- Deleting a customer removes their tickets too. Both removals are reported, each on its own resource’s sync, so an integration that syncs only tickets still hears about it.
If you would rather be told as it happens, subscribe to the customer.deleted and
ticket.deleted webhooks — they carry the same record, with the same
removedAt.