The Sensay API uses offset-based pagination for most list endpoints, providing a familiar page-based navigation pattern that's easy to implement in user interfaces.
How offset-based pagination works
Offset-based pagination uses page numbers and page sizes, making it familiar and easy to implement UI components like page selectors and "showing X of Y results" displays.
Parameters
page
: The page number (starting from 1)pageSize
: Number of items per page (typically 1-100)
The response includes a total
field showing the complete number of available items.
Example: List conversations
curl -X GET "https://api.sensay.io/v1/replicas/{replicaUUID}/conversations?page=2&pageSize=20" \
-H "X-ORGANIZATION-SECRET: $ORGANIZATION_SECRET" \
-H "X-API-Version: $API_VERSION" \
-H "Content-Type: application/json"
Response:
{
"success": true,
"items": [
{
"uuid": "8e9309af-baa4-4a85-8c59-c3a2a0c2ad0f",
"source": "web",
"messageCount": 42,
"firstMessageAt": "2025-05-27T15:02:44.499744+00:00",
"lastMessageAt": "2025-05-27T15:02:44.499744+00:00"
}
],
"total": 156
}
Implementation pattern
// Calculate pagination info
const totalPages = Math.ceil(response.total / pageSize);
const hasNextPage = currentPage < totalPages;
const hasPrevPage = currentPage > 1;
// Navigation
const nextPageUrl = hasNextPage ?
`/conversations?page=${currentPage + 1}&pageSize=${pageSize}` : null;
const prevPageUrl = hasPrevPage ?
`/conversations?page=${currentPage - 1}&pageSize=${pageSize}` : null;
// Display info
const startItem = (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, response.total);
console.log(`Showing ${startItem}-${endItem} of ${response.total} results`);
Building pagination UI
function createPaginationControls(currentPage, totalPages) {
const controls = [];
// Previous button
if (currentPage > 1) {
controls.push({
type: 'previous',
page: currentPage - 1,
label: 'Previous'
});
}
// Page numbers (show 5 pages max)
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
for (let page = startPage; page <= endPage; page++) {
controls.push({
type: 'page',
page: page,
label: page.toString(),
active: page === currentPage
});
}
// Next button
if (currentPage < totalPages) {
controls.push({
type: 'next',
page: currentPage + 1,
label: 'Next'
});
}
return controls;
}
Best practices
Performance
Choose appropriate page sizes:
- 10-50 items for UI lists with good user experience
- Up to 100 for data processing or admin interfaces
- Avoid very large page sizes that may cause timeouts
Cache responses when possible:
- List endpoints often return stable data
- Cache total counts separately as they change less frequently
Error handling
async function fetchPage(page, pageSize) {
try {
const response = await fetch(`/conversations?page=${page}&pageSize=${pageSize}`);
if (!response.ok) {
if (response.status === 404) {
// Page doesn't exist, redirect to last valid page
return fetchPage(1, pageSize);
}
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Pagination error:', error);
throw error;
}
}
UI considerations
- Loading states: Show loading indicators during page transitions
- Empty states: Handle cases where no items are returned (page 1 with 0 total)
- Invalid pages: Gracefully handle requests for pages that don't exist
- URL synchronization: Keep browser URL in sync with current page for bookmarking