Paging through results
Every list endpoint returns its results a page at a time:
limitis the number of records in a page: 20 if you leave it out, and at most 1,000.offsetis the number of records to skip before the page starts.offset+limitcan’t exceed 5,000. A request beyond that is refused.
So paging by offset reaches the first 5,000 records of a result. That covers most uses, such as showing a list a page at a time. To read more than 5,000 records, for example to export or reconcile a whole history, page by a field instead.
Paging by a field
Section titled “Paging by a field”Sort by a field that doesn’t change, and start each page where the last one ended, with criteria rather than offset. created works for every object.
-
Ask for the first page, sorted by
created, oldest first:GET /transactions?order=created&limit=1000 -
Note the
createdvalue of the last record in the page. -
Ask for the next page from that value, with no
offset:GET /transactions?order=created&limit=1000&criteria=created>=2026-03-14T09:21:07Z -
Skip any records at the start of the page that you already have (match them by
id), then repeat from step 2 until a page has fewer records than yourlimit.
Use >=, not >, and skip the records you’ve already seen: several records can share the same created time, and > would miss the ones after the page boundary. Keep the rest of your criteria the same on every request, combined with commas:
criteria=status=SUCCESS,created>=2026-03-14T09:21:07ZRemember to URL encode criteria (see List APIs).
Example
Section titled “Example”// Reads every successful transaction, a page at a time, without offsetconst BASE = `https://app.shuttleglobal.com/c/api/instances/${process.env.SHUTTLE_INSTANCE_KEY}`const AUTH = `Basic ${Buffer.from(`${process.env.SHUTTLE_SECRET_KEY}:`).toString('base64')}`const LIMIT = 1000
async function* allTransactions() { const seen = new Set() let from = null while (true) { const criteria = ['status=SUCCESS', from && `created>=${from}`].filter(Boolean).join(',') const params = new URLSearchParams({ order: 'created', limit: String(LIMIT), criteria }) const res = await fetch(`${BASE}/transactions?${params}`, { headers: { Authorization: AUTH } }) if (!res.ok) throw new Error(`Shuttle returned ${res.status}`) const { transactions } = await res.json() for (const t of transactions) { if (seen.has(t.id)) continue seen.add(t.id) yield t } if (transactions.length < LIMIT) return from = transactions[transactions.length - 1].created }}
for await (const t of allTransactions()) console.log(t.id, t.amount, t.currency)For a very long history, keep only the ids that share the latest created time rather than every id you’ve seen.
Which field to page by
Section titled “Which field to page by”createdsuits most cases: it never changes once a record exists.- Avoid fields that change, such as
updatedorstatus: a record that changes while you page can move to a page you’ve already read, or appear twice. - To read newest first, sort with
order=created:descand usecreated<=instead ofcreated>=.
Was this page helpful?
What went wrong?
Thanks for telling us.