Pagination
How list endpoints are paginated across the Tidepay API.
Every list endpoint (GET /offers, /customers, /subscriptions, /payments) shares the same offset-based pagination — two query parameters, limit and start, and results ordered newest first.
Offset, not cursor
Unlike APIs that paginate by object id (starting_after/ending_before),
Tidepay pages by numeric offset. There's no has_more flag in the response —
see Knowing when you've reached the end
below for how to detect the last page.
Parameters
-
limit(optional, default20) Max number of objects to return, from1to100. Requesting more than100is rejected with400, not silently capped. -
start(optional, default0) The offset into the result set. A call with no query params returns the newest20rows (start=0). To fetch the next page, passstartequal to the number of rows you've already seen — e.g. after a first page of20, pass?start=20for the second page.
Results are ordered newest first (most recently created row first), so start always counts from the most recent end of the list, not the oldest.
Response shape
There's no shared object: "list" envelope — each endpoint nests its array under a key named for the resource, alongside the limit/start you requested:
{
"customers": [
{
"id": "cust_01j8x2q3r4s5t6u7v8w9x0y1z2",
"walletAddress": "0x71c7656ec7ab88b098defb751b7401b5f6d8976",
"merchantReferenceId": null,
"email": "payer@example.com",
"phone": null,
"name": null,
"isSandbox": false,
"idempotencyKey": null,
"createdAt": "2026-08-15T09:12:03.000Z",
"updatedAt": "2026-08-15T09:12:03.000Z"
},
{
"id": "cust_01j8x1a2b3c4d5e6f7g8h9j0k1",
"walletAddress": null,
"merchantReferenceId": "ORD-5521",
"email": null,
"phone": "+5511998887766",
"name": "Jane Doe",
"isSandbox": false,
"idempotencyKey": null,
"createdAt": "2026-08-14T22:41:17.000Z",
"updatedAt": "2026-08-14T22:41:17.000Z"
}
],
"limit": 2,
"start": 0
}The array key differs per endpoint — offers, customers, subscriptions, or payments — matching the resource being listed. limit/start in the response simply echo back what the request resolved to (including the defaults, if you omitted them), not anything computed from the result set.
Knowing when you've reached the end
Since there's no has_more field, infer it from the page size: if the array you got back has fewer items than the limit you requested (or asked for by omission — the default 20), you've reached the end of the list.
let start = 0;
const limit = 100;
let allCustomers: Customer[] = [];
while (true) {
const res = await fetch(
`https://api.tidepay.cc/v1/customers?limit=${limit}&start=${start}`,
{ headers: { "X-API-Key": apiKey } },
);
const page = await res.json();
allCustomers = allCustomers.concat(page.customers);
if (page.customers.length < limit) break; // last page
start += limit;
}Pages can drift under concurrent writes
Offset pagination has a known tradeoff: if a row is created or deleted between
two page requests, results ordered by createdAt can shift — a row might be
skipped, or seen twice, across pages. This is rarely an issue for periodic
sync jobs, but if you need a guaranteed-consistent full export, prefer
filtering by createdAt/updatedAt bounds you control (where the endpoint
supports it) over paging through the entire history in one pass.
Filtering
Some list endpoints accept additional query parameters, always combined with — never instead of — limit/start:
| Endpoint | Extra filters |
|---|---|
GET /offers | ?active=true / ?active=false, ?identifier= |
GET /customers | ?walletAddress=0x... |
GET /subscriptions | ?status=, ?customerId=, ?planId= |
GET /payments | ?status=, ?source=, ?customerId=, ?subscriptionId=, ?offerId= |
All filters are optional and narrow the result set before limit/start are applied — start=20 after adding a filter means the 20th row matching that filter, not the 20th row overall.
Related pages
Every list endpoint is also subject to a request-rate quota, separate from
pagination — see Consumption limits. The
exact response schema for each resource (every field in a Plan, Customer,
etc.) is in the API reference.