Pagination
Every list that can grow without bound is cursor-paginated: conversations, messages, the audit trail and an endpoint's deliveries. You ask for a page, get the items and an opaque cursor, and pass the cursor back for the next page.
How cursors work#
A list request takes limit (1 to 100, default 50; larger values are clamped) and cursor. The response carries the items under the resource name and next_cursor, which is null on the last page. The cursor is opaque and stable under concurrent writes, so a walk that started before new rows arrived still finishes cleanly.
{
"ok": true,
"data": {
"conversations": [
{
"id": "CONV#acme#instagram#17841405309211844#6021573449812077",
"platform": "instagram",
"account_handle": "acme.ink",
"participant_handle": "sam.draws",
"last_message_text": "Do you have anything on Saturday?",
"window": { "state": "open_24h", "expires_at": "2026-09-04T03:14:22Z" },
"unanswered": true,
"can_reply": true,
"can_send_as_human_agent": true
}
],
"next_cursor": "eyJwayI6IkNPTlYjYWNtZSNpbnN0YWdyYW0jMSMyIn0"
}
}Walk pages by hand#
The loop is the same in every language: request, handle the items, stop when there is no cursor. A small limit here makes the paging visible; use 100 in real code.
import { HookChat } from '@hookchat/node'
const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })
let cursor: string | undefined
let total = 0
let pages = 0
do {
const page = await client.conversations.list({ limit: 2, cursor })
total += page.conversations.length
pages += 1
cursor = page.next_cursor ?? undefined
} while (cursor)
console.log(total, 'conversations over', pages, 'pages')Let the SDK iterate#
Each paginated resource has an iterator that follows the cursor for you and yields one item at a time. Pages are fetched lazily, so breaking out of the loop stops the requests, and a maximum item count bounds the walk.
import { HookChat } from '@hookchat/node'
const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })
let count = 0
for await (const conversation of client.conversations.iterate({ limit: 50, maxItems: 10 })) {
console.log(conversation.id, conversation.window.state, conversation.unanswered ? 'unanswered' : '')
count += 1
}
console.log(count, 'conversations')Filter the message feed#
The tenant-wide message feed is newest first and takes two filters: conversation_id narrows it to one thread and direction to inbound or outbound. A filtered page can be short and still carry a cursor, so always loop on the cursor rather than on the page size. Rows written by test-scope keys appear only with include_test.
import { HookChat } from '@hookchat/node'
const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })
const page = await client.messages.list({
conversation_id: process.env.CONVERSATION_ID!,
direction: 'inbound',
limit: 5,
})
for (const message of page.messages) console.log(message.id, message.direction, message.sent_at, message.text)
console.log(page.messages.length, 'inbound messages')Limits and the unpaginated lists#
limitis 1 to 100. The server clamps larger values to 100 rather than rejecting them.- Paginated: conversations, messages, audit entries, and an endpoint's deliveries. Each has an iterator in every SDK and
--allin the CLI. - Not paginated: accounts, keys and webhook endpoints. They return the whole list.
- The SDK iterators raise an error if a cursor does not advance, so a server defect cannot loop forever.