Reference
Pagination
A keyset cursor over (name, id). Stable while products are added or retired, and opaque by agreement.
How it behaves#
- Order is fixed:
nameascending, withproductIdbreaking ties. Two products may share a name; the id makes the order total. next_cursoris null exactly when there is no next page.truncatedcarries the same fact for readers that already use it, sotruncated === (next_cursor !== null)always holds.- The cursor names the last row you received, never a look-ahead row. A page that ends on a boundary returns
next_cursor: nulland no empty final page. - Pass it back verbatim. Do not decode it, build one, or edit it.
- A cursor you did not receive is a
400. It is never silently ignored, because a dropped cursor would restart the walk at page one forever.
Omit
cursor entirely on the first request. Sending cursor= with an empty value is a 400, on purpose.A full walk#
let cursor = null;
const products = [];
for (;;) {
const qs = new URLSearchParams({ limit: '100' });
if (cursor) qs.set('cursor', cursor);
const res = await fetch(`${BASE}/products?${qs}`, {
headers: { Authorization: `Bearer ${process.env.TRIMFITT_PARTNER_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const page = await res.json();
products.push(...page.products);
cursor = page.next_cursor;
if (cursor === null) break;
}The one caveat: renames#
The sort key is name, which is mutable. If a product is renamed between two page requests, it can appear twice or be missed once, by exactly the rows the rename moves across the page boundary. Reconcile on productId, which is stable.
That is the trade for not skipping rows when products are added or retired mid-walk, which an offset does.
Related: the catalogue fields and errors.