Pagination
Navigate through large result sets with cursor-based pagination
All list endpoints use cursor-based pagination for consistent, efficient traversal of large datasets.
How It Works
Instead of page numbers, cursors point to a specific position in the result set. This avoids issues with data changing between page requests.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 25 | Number of results per page (max: 100) |
cursor | string | — | Opaque cursor from a previous response |
Response Format
List responses include a pagination object in meta:
{
"data": [...],
"meta": {
"request_id": "req_abc123def456ghi789jkl012",
"pagination": {
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNVQxMDozMDowMFoiLCJpZCI6ImN0XzVoSjNrTTlwUXJWd1h5WmFCMmNENCJ9",
"has_more": true
}
}
}| Field | Type | Description |
|---|---|---|
next_cursor | string | null | Cursor to fetch the next page, null if no more results |
has_more | boolean | true if there are more results after this page |
Basic Pagination
# First page
curl "https://api.nodestash.io/v1/contacts?limit=25" \
-H "Authorization: Bearer $NODESTASH_API_KEY"
# Next page (use next_cursor from previous response)
curl "https://api.nodestash.io/v1/contacts?limit=25&cursor=eyJjcmVh..." \
-H "Authorization: Bearer $NODESTASH_API_KEY"// First page
const page1 = await client.contacts.list({ limit: 25 })
console.log(page1.data) // Contact[]
console.log(page1.pagination.has_more) // true
console.log(page1.pagination.next_cursor) // "eyJjcmVh..."
// Next page
if (page1.pagination.has_more) {
const page2 = await client.contacts.list({
limit: 25,
cursor: page1.pagination.next_cursor!,
})
}Manual Page-by-Page Loop
#!/bin/bash
cursor=""
page=1
while true; do
if [ -z "$cursor" ]; then
response=$(curl -s "https://api.nodestash.io/v1/contacts?limit=100" \
-H "Authorization: Bearer $NODESTASH_API_KEY")
else
response=$(curl -s "https://api.nodestash.io/v1/contacts?limit=100&cursor=$cursor" \
-H "Authorization: Bearer $NODESTASH_API_KEY")
fi
echo "Page $page: $(echo $response | jq '.data | length') contacts"
has_more=$(echo $response | jq -r '.meta.pagination.has_more')
cursor=$(echo $response | jq -r '.meta.pagination.next_cursor')
if [ "$has_more" = "false" ]; then
break
fi
page=$((page + 1))
donelet cursor: string | undefined
let page = 1
do {
const result = await client.contacts.list({ limit: 100, cursor })
console.log(`Page ${page}: ${result.data.length} contacts`)
cursor = result.pagination.next_cursor ?? undefined
page++
} while (cursor)Auto-Pagination with Async Iterators
The TypeScript SDK provides a listAll() method that automatically handles pagination using async generators:
// Iterate through every contact — pagination is handled automatically
for await (const contact of client.contacts.listAll()) {
console.log(`${contact.first_name} ${contact.last_name}`)
}
// With filters and custom page size
for await (const deal of client.deals.listAll({
pipeline_id: 'pp_abc123',
sort: '-value',
limit: 100,
})) {
console.log(`${deal.title}: ${deal.value} ${deal.currency}`)
}
// Collect all results into an array
const allContacts: Contact[] = []
for await (const contact of client.contacts.listAll()) {
allContacts.push(contact)
}listAll() fetches pages lazily — it only requests the next page when the current one is exhausted. This keeps memory usage low even for large datasets.
Combining Pagination with Filters
Filters and sorting work seamlessly with pagination:
curl "https://api.nodestash.io/v1/contacts?tags=vip&sort=-created_at&limit=50" \
-H "Authorization: Bearer $NODESTASH_API_KEY"const { data, pagination } = await client.contacts.list({
tags: 'vip',
sort: '-created_at',
limit: 50,
})Best Practices
- Use
listAll()in the SDK — it handles pagination automatically and is the simplest approach - Set a reasonable
limit— use100for bulk operations,25for UI pagination - Don't store cursors long-term — cursors are opaque and may expire
- Combine with filters — narrow your results before paginating to reduce total pages