Pagination & sync
Listing endpoints in the MendriX REST API use cursor-based pagination and
support incremental sync with a since= parameter. Both are described in
the API reference; this page explains the pattern.
Paging with cursor and limit
Section titled “Paging with cursor and limit”| Parameter | Meaning | Default | Max |
|---|---|---|---|
limit | Page size | 100 | 1000 |
cursor | Opaque pointer to the next page | — | — |
sort | Sort field; prefix with - for descending | endpoint default | — |
A few endpoints cap limit lower because their responses carry more per item —
purchase line items
defaults to 10 and caps at 100. The per-endpoint values are in the
reference; a larger limit is accepted but clamped.
To page through a listing, request the first page, then keep following the cursor returned with each response until no further cursor is given.
# First pagecurl "https://<your-client-name>.api.mendrix.cloud/api/client/clients/?limit=100" \ -H "Authorization: Bearer ACCESS_TOKEN"
# Next page — pass the cursor returned by the previous responsecurl "https://<your-client-name>.api.mendrix.cloud/api/client/clients/?limit=100&cursor=NEXT_CURSOR" \ -H "Authorization: Bearer ACCESS_TOKEN"Loop through every page (JavaScript)
Section titled “Loop through every page (JavaScript)”const BASE = 'https://<your-client-name>.api.mendrix.cloud/api';const TOKEN = process.env.MENDRIX_ACCESS_TOKEN; // from login-api-token
// Yields every item; stamps the cycle's maxSequence on `state` for the next sync.async function* listAll(path, params = {}, state = {}) { let cursor = ''; let hasMore = true; while (hasMore) { const qs = new URLSearchParams({ limit: '1000', ...params }); if (cursor) qs.set('cursor', cursor);
const res = await fetch(`${BASE}${path}?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}`, Accept: 'application/json' }, }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { data } = await res.json(); for (const item of data.items ?? []) yield item;
// Follow the cursor until the API says there is no more. cursor = data.nextCursor; hasMore = data.hasMore; state.maxSequence = data.maxSequence; }}
for await (const client of listAll('/client/clients/')) { console.log(client.id);}Incremental sync with since=
Section titled “Incremental sync with since=”For synchronisation, use since= to fetch only records changed since your last
run, instead of re-reading everything each time.
since=is a sequence number, not a date: pass back themaxSequencethe API returned in the envelope of your previous cycle. It is exclusive — you get the records whose sequence is greater than the value you pass.- Only a request carrying
since=returns a watermark you can persist. Withsince=,maxSequenceis a snapshot frozen at the start of the cycle, identical on every page. Without it,maxSequenceis just the highest sequence among the items on the page you happen to have fetched (and is omitted when the page carries none) — informational, not a high-water mark. - Bootstrap the first cycle with
since=1: it returns everything and hands you a real snapshot to store. maxSequenceis constant across a cycle, so persist it once, after the last page (hasMoreisfalse). Tracking a per-page maximum is unnecessary.since=requires the endpoint’s default sort (by id). Combining it with a customsortreturns400.
GET /client/clients/?since=41207&limit=1000GET /client/clients/?since=1&limit=1000 # first cycle: everything, plus a watermarkIncremental sync loop
Section titled “Incremental sync loop”// Pseudocode for a recurring sync job.const since = loadWatermark() || 1; // sequence number; 1 on the first run ever
const state = {};for await (const record of listAll('/client/clients/', { since }, state)) { upsertIntoLocalStore(record); // your side}
// Trustworthy because this walk passed since=; a walk without it reports only// the last page's maximum.if (state.maxSequence) saveWatermark(state.maxSequence);Tips
- Run on a schedule (e.g. every few minutes) rather than continuously.
- Always use the largest sensible
limit(up to1000) to reduce round trips. - Make your upsert idempotent — a record may be seen again if it changes on a boundary, and some endpoints re-deliver a whole group when one member changes.
- Deletions need care. Hard-deleted rows are not surfaced by
since=. Where an endpoint supports?status=deleted, soft-deletes do bump the sequence and show up in the next sync; otherwise, run a periodic full re-read to notice removals. - For near-real-time updates, combine polling with
webhooks: react to webhook events immediately
and use the
since=sweep as a safety net.