Pagination
Page through lists with opaque keyset cursors, page through reports with limit and offset, and pull large data sets without gaps or duplicates.
Last updated 2 min read
On this page
The API uses two kinds of paging:
/deals, /product-feeds and /offers/{id}/creatives return everything in one response (next_cursor is always
null). A click lookup by gclid / gbraid / wbraid returns up to limit rows (default 100, max 1,000) without
a cursor.
Cursor pagination#
A list response contains a page of results and an opaque cursor:
{
"data": [ { "id": 12, "…": "…" } ],
"next_cursor": "eyJrIjoib2ZmZXJzIiwidCI6IjIwMjYtMTAtMDFUMDk6MzA6MDAuMDAwMDAwWiIsImkiOiIxMiJ9"
}- Request the first page with your filters and a
limit. - If
next_cursoris notnull, request the same URL with&cursor=<next_cursor>. - Stop when
next_cursorisnull.
Rules:
- Keep the filters identical between pages. The cursor stores only a position.
- Results are newest first — by creation time, or by click time (clicks) and conversion time (conversions).
- The cursor is a keyset position, so rows created while you page never shift or repeat results.
- A cursor from one list is refused by another:
400 invalid_cursor. Treat cursors as opaque strings; their format may change.
Page sizes#
Time-ranged lists#
/clicks and /conversions page within a date range (from/to/preset). Defaults: today for clicks (at most
93 days per request) and the last 7 days for conversions (at most 400 days). For longer click periods, use
reports, or request one range at a time.
Offset pagination (reports)#
GET /reports returns aggregated rows with limit (default 1,000, max 10,000) and offset:
curl "https://app.trackcrown.com/api/v1/reports?preset=last30&group_by=date,offer&metrics=clicks,conversions,revenue&limit=10000&offset=0" \
-H "Authorization: Bearer $ATK"The response carries total_rows and next_offset — the offset of the next page, or null on the last page (and
always null for a report without group_by). Totals in totals are computed by the database for the whole report,
not per page.
For a full export use format=csv instead: it streams every row (up to 200,000) plus a totals line in one request.
Example: nightly conversion sync (bash)#
#!/usr/bin/env bash
# Pull yesterday's approved conversions page by page (needs curl and jq).
set -euo pipefail
ATK="atk_…"
URL="https://app.trackcrown.com/api/v1/conversions?preset=yesterday&status=approved&limit=500"
CURSOR=""
while :; do
PAGE=$(curl -sf "$URL${CURSOR:+&cursor=$CURSOR}" -H "Authorization: Bearer $ATK")
echo "$PAGE" | jq -c '.data[]'
CURSOR=$(echo "$PAGE" | jq -r '.next_cursor // empty')
[ -z "$CURSOR" ] && break
doneExample: all clicks of a day (Python)#
import os, requests
BASE = "https://app.trackcrown.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ATK']}"}
def clicks(day: str):
params = {"from": day, "to": day, "limit": 1000}
while True:
r = requests.get(f"{BASE}/clicks", params=params, headers=HEADERS, timeout=60)
r.raise_for_status()
page = r.json()
yield from page["data"]
if not page["next_cursor"]:
return
params["cursor"] = page["next_cursor"]
for c in clicks("2026-10-11"):
print(c["click_id"], c["gclid"], c["ext_campaign_id"])Example: a large report (Node.js)#
async function report(query) {
const rows = [];
let offset = 0;
while (offset !== null) {
const res = await fetch(`https://app.trackcrown.com/api/v1/reports?${query}&limit=10000&offset=${offset}`, {
headers: { Authorization: `Bearer ${process.env.ATK}` },
});
const page = await res.json();
rows.push(...page.data);
offset = page.next_offset;
}
return rows;
}
const rows = await report("preset=last_month&group_by=date,affiliate&metrics=clicks,conversions,payout");See also: Rate limits & errors · API reference.