Skip to content
TRIM FITTv1 · read
↑↓ to move↵ to openesc to close
Contents

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: name ascending, with productId breaking ties. Two products may share a name; the id makes the order total.
  • next_cursor is null exactly when there is no next page. truncated carries the same fact for readers that already use it, so truncated === (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: null and 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#

TypeScript
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.