Pagination
How list endpoints page, sort, and filter results.
Envelope
Every list endpoint returns the same envelope:
{
"data": [
/* items */
],
"meta": {
"page": 1,
"limit": 20,
"total": 1247,
"totalPages": 63
}
}data— array of resource objects, never nullmeta.page— 1-indexed current pagemeta.limit— page size actually applied, which may be lower than what you asked for (see the cap below)meta.total— total count across all pagesmeta.totalPages—Math.ceil(total / limit)
Query params
GET /v1/products?page=2&limit=100&search=shirt&sortBy=createdAt&sortOrder=desc| Param | Type | Default | Notes |
|---|---|---|---|
page | number | 1 | 1-indexed |
limit | number | 20 | Hard cap 100. A larger value is silently clamped, not rejected |
search | string | — | Substring match across name + key text fields |
sortBy | string | per-endpoint | Allowlisted per endpoint — an unrecognised value is ignored, not rejected |
sortOrder | string | per-endpoint | asc or desc |
The cap is 100 and it does not error.
?limit=200returns 100 rows withmeta.limit: 100. Nothing in the response says you asked for more. Any loop that decides it has reached the end by comparingdata.lengthagainst the limit it requested will stop after the first page — see the iteration example below.
Sorting
Each endpoint allows a specific set of sortBy values. Products, for example, accepts
name, createdAt, updatedAt, menuOrder, status, sku, slug, basePrice
(plus the aliases price → basePrice), defaults to menuOrder, and defaults
sortOrder to asc.
An unsupported sortBy value is not an error. There is no 400 and no error code —
the server falls back to its own default ordering (createdAt on Products) and returns
200. If your integration depends on a particular order, assert on the data you get
back rather than trusting that a typo'd sort field would have failed loudly.
Iterating through all results
For backfills and exports, drive the loop from meta.totalPages only:
let page = 1;
while (true) {
const res = await client.getProducts({ page, limit: 100 }); // 100 is the cap
for (const p of res.data) await handle(p);
if (page >= res.meta.totalPages) break;
page++;
}Do not add res.data.length < limit as a break condition. If you requested more
than 100 it is true on page 1 and the backfill silently skips the rest of the catalog.
There is no iterate() / async-generator helper in the SDK today — write the loop above.
No cursor pagination
Every list endpoint is page-based. There is no cursor query parameter and no
meta.nextCursor field anywhere on the public API — if you have seen those in an older
version of this page, they were never implemented.
The practical consequence: paging is not stable under concurrent writes. Rows inserted or deleted while you iterate can shift items across page boundaries, so a long backfill can repeat or miss a row. Two mitigations that do work today:
- Sort by an immutable field (
createdAt) rather than one that changes (updatedAt,menuOrder). - Make the consumer idempotent — key on the resource id and upsert, so a repeated row is harmless.
Filtering
Filters are endpoint-specific — see each endpoint's docs page. Common patterns:
?status=active— exact match?categories=cat_a,cat_b— comma-separated OR?minPrice=10&maxPrice=50— numeric ranges?search=shirt— substring match