Skip to content

Pagination

Every list endpoint in the SIPSTACK Developer API returns results in pages, newest first. There are two paging modes: offset paging (page / limit, with a running total) and keyset paging (an opaque next_cursor). Offset paging is available on every list endpoint; keyset paging is available on selected high-volume list endpoints and is the safer choice for walking large result sets.

Pass page (1-based) and limit. The response carries a pagination object describing where you are:

GET /api/v2/contacts?page=2&limit=50
x-api-key: sk_live_...
{
"success": true,
"data": [ /* up to `limit` records */ ],
"pagination": {
"page": 2,
"limit": 50,
"total": 143,
"totalPages": 6
}
}
FieldMeaning
pageThe page you requested (1-based)
limitItems per page
totalTotal matching records across all pages
totalPagesceil(total / limit) — the last page number

Walk pages by incrementing page until page === totalPages (or data comes back empty).

Offset paging is convenient (you get a total and can jump to any page) but it is not stable under writes: if new rows arrive between requests, records shift across page boundaries and you can see a row twice or skip one. For a live, growing dataset — message and call history especially — prefer keyset paging.

Keyset paging walks the result set by an opaque cursor anchored to the last row you saw, so new rows arriving at the top never cause drift or duplicates. Instead of page, pass the cursor you got from the previous response’s next_cursor:

GET /api/v2/messages?limit=100
x-api-key: sk_live_...
{
"success": true,
"data": [ /* up to `limit` records */ ],
"pagination": { "page": null, "limit": 100, "total": null, "totalPages": null },
"next_cursor": "eyJpZCI6MTAwMjR9"
}

Feed next_cursor back as cursor to fetch the next page:

GET /api/v2/messages?limit=100&cursor=eyJpZCI6MTAwMjR9
  • When cursor is set, the request runs in keyset mode and page, total, and totalPages come back null — there is no total-record count in cursor mode.
  • next_cursor is null on the last page. Stop when it’s null.
  • A malformed or expired cursor returns 400.
async function* allMessages(limit = 100) {
let cursor;
do {
const url = new URL('https://api.sipstack.com/api/v2/messages');
url.searchParams.set('limit', String(limit));
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { 'x-api-key': process.env.SIPSTACK_API_KEY } });
const body = await res.json();
yield* body.data;
cursor = body.next_cursor; // null on the last page
} while (cursor);
}

Keyset (cursor / next_cursor) paging is available on these four list endpoints:

EndpointProductOffsetKeyset
GET /api/v2/messagesFlare✓✓
GET /api/v2/callsNova✓✓
GET /api/v2/contactsFlare✓✓
GET /api/v2/webhooksWebhooks✓✓

Every other list endpoint — Nova call/number/voicemail lists, Aura call lists, Flare campaign and contact-list lists, webhook delivery logs, and the rest — supports offset paging only (page / limit). Passing a cursor to an offset-only endpoint has no effect. Consult the API Reference for each endpoint’s exact parameters.

  • Use keyset paging for history — message and call lists grow constantly; next_cursor won’t skip or double-count rows.
  • Request the maximum limit — fewer round trips, fewer rate-limit hits.
  • Stop on the signal, not a guess — offset: page === totalPages; keyset: next_cursor === null.
  • Never store a cursor long-term — it’s opaque and tied to a query; re-list from the top if you need to resync.