Pagination

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

  1. 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
  2. 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

  1. Loading states: Show loading indicators during page transitions
  2. Empty states: Handle cases where no items are returned (page 1 with 0 total)
  3. Invalid pages: Gracefully handle requests for pages that don't exist
  4. URL synchronization: Keep browser URL in sync with current page for bookmarking