Pagination
List endpoints return at most 500 items per page (default 100). Use ?cursor= to walk pages; cursors are opaque and ordering is stable across requests.
List response shape
{
"object": "list",
"livemode": true,
"data": [ /* up to <limit> items */ ],
"has_more": true,
"next_cursor": "eyJhdCI6IjIwMjYtMDQtMjdUMTM6MDI6MDBaIiwiaWQiOiJyY3BfMDE4ZjVhMmU3YzEyN2M4ZGExMjNjYWZlZDAwZGZlZWQifQ",
"url": "/v1/partners/stitched/tokens?since=2026-04-01T00:00:00Z&limit=100"
}data— the page of resources, in stable order.has_more—truewhen at least one more page exists.next_cursor— opaque base64url string. Pass back as?cursor=on the next request.nullwhenhas_moreis false.url— echo of the request path + query string, for client-side correlation.livemode—trueon the production deployment,falseon staging/preview.
Walking pages
async function* paginate(url, headers) {
let cursor = null
while (true) {
const u = new URL(url)
if (cursor) u.searchParams.set('cursor', cursor)
const r = await fetch(u, { headers })
const page = await r.json()
for (const row of page.data) yield row
if (!page.has_more) return
cursor = page.next_cursor
}
}
for await (const record of paginate(
'https://api.tendralhealth.com/v1/partners/stitched/tokens?since=2026-04-01T00:00:00Z&limit=500',
{ Authorization: `Bearer ${process.env.TENDRAL_API_KEY}` },
)) {
console.log(record.tendral_recipient_id, record.token)
}Cursor stability
Cursors are composite over (updated_at, id) for token lists, (occurred_at, event_id) for events, and (created_at, id) for recipients. This guarantees stable ordering even when many rows share a millisecond timestamp.
Treat cursors as opaque. Decoding them and editing the contents is unsupported and may break across deploys.
Limits
- Default
limit=100. - Max
limit=500. Larger values return400 INVALID_PARAMETER. - Smaller pages are fine (e.g.
limit=10) — every page still carries the same envelope.
New rows during walk
Cursor pagination is point-in-time consistent within a single walk: rows added after you started will appear on a later ?since= poll, not on the current cursor walk. This means a polling consumer should:
- Walk pages with the current
since=<last-checkpoint>untilhas_more=false. - Record the timestamp of the latest item just received as the new checkpoint.
- Sleep, then repeat with
since=<new-checkpoint>.
Combined with HMAC-verified webhooks and event_id deduplication, this is the recommended belt-and-suspenders pattern for the reconciliation feed.
