Pagination
Three styles co-exist in the API. They're not interchangeable — each endpoint uses one, and the response envelope tells you which.
| Style | Used by | Why |
|---|---|---|
Page-based (?page=N&limit=M) | GET /transactions, GET /customers, GET /payment-links | Stable, sortable by created_at, suits dashboard-style "page 1 of 7" UIs |
Limit/Offset (?limit=M&offset=N) | GET /subscriptions | Lets background workers walk forward by chunks without holding state |
| Implicit / unpaginated | GET /customers/{id}/payment-tokens, GET /zapier/sample-payments | Always small (saved cards per customer, 3-record samples) |
All paginated endpoints clamp limit to a max of 100 and default to 25 (or 20 for /subscriptions).
Page-based
GET /api/v1/transactions?page=2&limit=50&status=captured{
"data": [ /* up to 50 transactions */ ],
"meta": {
"current_page": 2,
"last_page": 14,
"per_page": 50,
"total": 692
}
}Walk forward by incrementing page until current_page === last_page. Adding from / to date filters narrows the set and reduces last_page accordingly.
Limit/Offset
GET /api/v1/subscriptions?status=active&limit=50&offset=100{
"data": [ /* up to 50 subscriptions */ ],
"total": 412,
"limit": 50,
"offset": 100
}Walk forward by adding limit to offset until offset + limit >= total. Stable across calls as long as no new subscriptions are inserted during the walk; for nightly exports we recommend pinning a created_at upper bound via a date filter on the underlying resource if available.
Choosing a page size
- For interactive UIs, default to 25 — keeps response payloads under ~200 KB.
- For background workers / nightly exports, prefer 100 — minimum round-trips.
limit > 100is clamped to 100; you'll get 100 rows andper_page: 100in the meta.
Code samples
// Node — walk every active subscription
async function walkSubscriptions(merchantKey) {
let offset = 0, limit = 100
while (true) {
const r = await fetch(
`https://app.geniuscheckout.com/api/v1/subscriptions?status=active&limit=${limit}&offset=${offset}`,
{ headers: { Authorization: `Bearer ${merchantKey}` } },
)
const { data, total } = await r.json()
for (const sub of data) yield sub
offset += limit
if (offset >= total) break
}
}// PHP — walk every transaction this month
$page = 1;
do {
$r = json_decode(file_get_contents(
"https://app.geniuscheckout.com/api/v1/transactions?page={$page}&limit=100&from=2026-05-01",
false,
stream_context_create(['http' => ['header' => "Authorization: Bearer {$key}"]])
), true);
foreach ($r['data'] as $txn) {
// …
}
$page++;
} while ($page <= $r['meta']['last_page']);Why three styles?
Different endpoints have different consistency needs. Money-table reads (/transactions, /customers) want stable page numbers for merchant dashboards. Background-job reads (/subscriptions) want offset cursors a worker can walk past while new records appear without skipping rows. We unified on the two styles that match real usage rather than forcing a single style where it doesn't fit. New endpoints lean toward limit/offset.
Next
- Rate Limits — list endpoints are in the
writestier
