Docs

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_moretrue when at least one more page exists.
  • next_cursor — opaque base64url string. Pass back as ?cursor= on the next request. null when has_more is false.
  • url — echo of the request path + query string, for client-side correlation.
  • livemodetrue on the production deployment, false on 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 return 400 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:

  1. Walk pages with the current since=<last-checkpoint> until has_more=false.
  2. Record the timestamp of the latest item just received as the new checkpoint.
  3. 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.