Skip to content
Status

Paging through results

Every list endpoint returns its results a page at a time:

  • limit is the number of records in a page: 20 if you leave it out, and at most 1,000.
  • offset is the number of records to skip before the page starts.
  • offset + limit can’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.

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.

  1. Ask for the first page, sorted by created, oldest first:

    GET /transactions?order=created&limit=1000
  2. Note the created value of the last record in the page.

  3. Ask for the next page from that value, with no offset:

    GET /transactions?order=created&limit=1000&criteria=created>=2026-03-14T09:21:07Z
  4. 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 your limit.

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:07Z

Remember to URL encode criteria (see List APIs).

// Reads every successful transaction, a page at a time, without offset
const 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.

  • created suits most cases: it never changes once a record exists.
  • Avoid fields that change, such as updated or status: 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:desc and use created<= instead of created>=.

Was this page helpful?