Pagination
Page through lists of messages, conversations, events and logs with cursors, without missing or repeating items.
What you'll build
A loop that reads a whole list one page at a time, and a poller that only fetches what is new.
Prerequisites
- A secret API key with the scope of the list you read (
messages:read,events:read,webhooks:manageorlogs:read). See Authentication and API keys. - curl, Node.js 18 or later with
@voicelayer/api, or Python 3.9 or later withvoicelayer.
How cursors work
Lists return a page and a cursor. Pass the cursor back to get the next page. Cursors are opaque: store them as they are, never build or change one. A cursor the API did not issue is refused with 400.
| List | Send | Read from the answer | Page size | Order |
|---|---|---|---|---|
GET /v1/messages, GET /v1/conversations/{id}/messages | after (newer), before (older, or end for the newest page) | nextCursor, prevCursor, hasMore | default 50, max 100 | oldest first |
GET /v1/conversations | cursor | nextCursor (null at the end) | default 50, max 100 | most recently active first |
GET /v1/events | after | cursor, hasMore | default 100, max 500 | oldest first |
GET /v1/request-logs | after (older), since (newer) | nextCursor, latestCursor | default 50, max 200 | newest first |
GET /v1/webhooks/deliveries | since, until (times) | — | default 50, max 200 | newest first |
Set the page size with limit.
Step 1: Read the first page
curl "https://api.vlayers.ai/v1/conversations?controller=human&limit=20" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"{
"conversations": [
{
"id": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
"channel": "sms",
"lineId": "0192f5a8-1a2b-7c3d-8e4f-5a6b7c8d9e01",
"bindingId": null,
"peer": {
"address": "+15550123"
},
"controller": "api",
"status": "open",
"openedAt": "2026-10-20T15:04:05.000Z",
"lastActivityAt": "2026-10-20T15:04:05.000Z",
"test": true
}
],
"nextCursor": null
}Step 2: Follow the cursor
Pass nextCursor back as cursor until it is null:
export NEXT_CURSOR=<the nextCursor you got>curl "https://api.vlayers.ai/v1/conversations?cursor=$NEXT_CURSOR&limit=50" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"Step 3: Show a chat history, newest first
For a conversation's messages, start with before=end for the newest page. Each page is still in time order (oldest first). Pass its prevCursor as before for the page before it; prevCursor is null when nothing older is left.
export CONVERSATION_ID=<the conversation's id>curl "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID/messages?before=end&limit=20" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"Step 4: Poll for what is new
Keep the last nextCursor (messages) or cursor (events) and pass it as after next time. On an empty page the cursor stays where it was, so a poller never loses its place. For events, add waitSec to wait for the next one instead of polling in a tight loop:
curl "https://api.vlayers.ai/v1/events?after=$CURSOR&waitSec=20" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"Test it
With a test key, simulate a few messages with POST /v1/test/inbound (see Test mode), then page through GET /v1/messages with limit=1.
Common errors
| Error | Status | How to fix |
|---|---|---|
invalid_request | 400 | Read message and details.issues (the validation issues, by path), fix the request and send it again. |
invalid_cursor | 400 | Pass back a cursor (pull) or an event id: line (stream) exactly as received, or leave it out to start afresh. |
cursor_expired | 410 | Page through GET /v1/events from the same cursor (the pull reads the whole retention), then reconnect the stream with its last cursor. |
Next steps
- Stream events: no polling at all.
- Rate limits.
- API reference.