Skip to content
Last updated

When you're working with lists of resources in the Sprinklr API—cases, messages, accounts, profiles—you'll often encounter datasets too large to return in a single response. Pagination breaks these large datasets into manageable chunks, making your integration faster, more memory-efficient, and more user-friendly.

Why pagination matters

Imagine requesting all customer service cases from a large enterprise. They might have tens of thousands of cases. If the API tried to return all of them at once:

  • The response would be massive - Several megabytes of JSON data
  • The request would be slow - Could take 10-30 seconds or timeout entirely
  • Your application would struggle - Parsing and processing that much data at once consumes memory
  • The user would wait - No results until everything loads

Pagination solves all of these problems by breaking the data into smaller "pages" that you can fetch and process one at a time—or in parallel if you need speed.

Think of pagination like reading a book. You don't need to read all 300 pages at once to start understanding the story. You read one page, then the next, progressively building your understanding. API pagination works the same way.

How Sprinklr API pagination works

The Sprinklr API uses offset-based pagination (also called "page number pagination"). This is the most common and straightforward pagination approach.

Here's how it works:

  • You specify which page number you want (starting from 0)
  • You specify how many items per page (the page size)
  • The API returns that page of results plus metadata telling you about the overall dataset

Example:

Dataset: 150 cases total
Your request: page=0, size=20

API returns:
- Items 1-20 (first page)
- Metadata: "You're on page 0 of 8 pages, 150 total items"

Next request: page=1, size=20
- Items 21-40 (second page)
- Metadata: "You're on page 1 of 8 pages, 150 total items"

Pagination parameters

Every paginated endpoint accepts these query parameters:

ParameterTypeDefaultRange/ConstraintsDescription
pageinteger00 to totalPages-1Page number (0-indexed, first page is 0)
sizeinteger201 to 100Number of items per page
sortstringvariesfield,directionSort field and direction (asc or desc)

Making a paginated request

Node.js:

const fetch = require('node-fetch');

async function getCases(page = 0, size = 20, sort = 'createdTime,desc') {
  const url = new URL('https://api-v3.sprinklr.com/api/v3/case/search');
  url.searchParams.append('page', page);
  url.searchParams.append('size', size);
  url.searchParams.append('sort', sort);

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Key': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      status: 'OPEN'
    })
  });

  return response.json();
}

// Get first page (20 cases)
const firstPage = await getCases(0, 20);
console.log(`Loaded ${firstPage.data.length} cases`);
console.log(`Total: ${firstPage.pagination.totalElements} cases across ${firstPage.pagination.totalPages} pages`);

Python:

import requests
from urllib.parse import urlencode

def get_cases(page=0, size=20, sort='createdTime,desc'):
    params = {
        'page': page,
        'size': size,
        'sort': sort
    }

    response = requests.post(
        f'https://api-v3.sprinklr.com/api/v3/case/search?{urlencode(params)}',
        headers={
            'Authorization': f'Bearer {access_token}',
            'Key': api_key,
            'Content-Type': 'application/json'
        },
        json={'status': 'OPEN'}
    )

    return response.json()

# Get first page (20 cases)
first_page = get_cases(0, 20)
print(f"Loaded {len(first_page['data'])} cases")
print(f"Total: {first_page['pagination']['totalElements']} cases across {first_page['pagination']['totalPages']} pages")

PHP:

<?php
function getCases($page = 0, $size = 20, $sort = 'createdTime,desc') {
    global $accessToken, $apiKey;

    $url = 'https://api-v3.sprinklr.com/api/v3/case/search?' .
           http_build_query([
               'page' => $page,
               'size' => $size,
               'sort' => $sort
           ]);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
        'Key: ' . $apiKey,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['status' => 'OPEN']));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}

// Get first page (20 cases)
$firstPage = getCases(0, 20);
echo "Loaded " . count($firstPage['data']) . " cases\n";
echo "Total: " . $firstPage['pagination']['totalElements'] . " cases across " .
     $firstPage['pagination']['totalPages'] . " pages\n";
?>

Java:

import java.net.http.*;
import java.net.URI;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

public class PaginationExample {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static Map<String, Object> getCases(int page, int size, String sort)
            throws Exception {
        String url = String.format(
            "https://api-v3.sprinklr.com/api/v3/case/search?page=%d&size=%d&sort=%s",
            page, size, sort
        );

        String requestBody = mapper.writeValueAsString(
            Map.of("status", "OPEN")
        );

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Authorization", "Bearer " + accessToken)
            .header("Key", apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        return mapper.readValue(response.body(), Map.class);
    }

    public static void main(String[] args) throws Exception {
        // Get first page (20 cases)
        Map<String, Object> firstPage = getCases(0, 20, "createdTime,desc");
        List<Map> data = (List<Map>) firstPage.get("data");
        Map<String, Object> pagination = (Map<String, Object>) firstPage.get("pagination");

        System.out.println("Loaded " + data.size() + " cases");
        System.out.println("Total: " + pagination.get("totalElements") +
                          " cases across " + pagination.get("totalPages") + " pages");
    }
}

Understanding the pagination response

Every paginated response includes both your data and a pagination object with metadata:

{
  "success": true,
  "data": [
    {
      "caseId": "CASE-12345",
      "subject": "Customer inquiry about refund",
      "status": "OPEN",
      "createdTime": "2024-03-01T10:30:00Z"
    },
    {
      "caseId": "CASE-12346",
      "subject": "Product delivery delayed",
      "status": "OPEN",
      "createdTime": "2024-03-01T10:25:00Z"
    }
    // ... 18 more cases
  ],
  "pagination": {
    "page": 0,
    "size": 20,
    "totalElements": 157,
    "totalPages": 8,
    "first": true,
    "last": false
  }
}

Pagination metadata fields

FieldTypeDescriptionExample
pageintegerCurrent page number (0-indexed)0 (first page)
sizeintegerItems per page20
totalElementsintegerTotal number of items across all pages157
totalPagesintegerTotal number of pages8
firstbooleantrue if this is the first pagetrue on page 0
lastbooleantrue if this is the last pagetrue on page 7 (for 8 pages)

Using this metadata:

const response = await getCases(0, 20);
const { data, pagination } = response;

// Check if there are more pages
if (!pagination.last) {
  console.log('There are more pages to fetch');
  const nextPage = await getCases(pagination.page + 1, pagination.size);
}

// Calculate how many items are on this page
const itemsOnThisPage = data.length;

// Calculate range of items being displayed
const startItem = pagination.page * pagination.size + 1;
const endItem = startItem + itemsOnThisPage - 1;
console.log(`Showing items ${startItem}-${endItem} of ${pagination.totalElements}`);

Sorting paginated results

Use the sort parameter to control the order of results. This is crucial for pagination because the sort order determines which items appear on which pages.

Sort syntax

Format: field,direction

  • field: The property name to sort by
  • direction: asc (ascending, A→Z, 0→9, oldest→newest) or desc (descending, Z→A, 9→0, newest→oldest)

Common sort fields

FieldDescriptionExample Values
createdTimeWhen the item was createdcreatedTime,desc (newest first)
modifiedTimeWhen the item was last updatedmodifiedTime,desc
nameAlphabetical by namename,asc (A to Z)
statusBy status (alphabetical)status,asc
priorityBy priority levelpriority,desc (high to low)

Sort examples

Get newest cases first:

const newestFirst = await getCases(0, 20, 'createdTime,desc');
// Returns: March 15, March 14, March 13, ...

Get cases alphabetically by subject:

const alphabetical = await getCases(0, 20, 'subject,asc');
// Returns: "Account issue", "Billing question", "Complaint", ...

Get recently updated cases:

const recentlyUpdated = await getCases(0, 20, 'modifiedTime,desc');
// Returns cases sorted by last modification time

Important: Always use the same sort order when fetching multiple pages. If you change the sort order between requests, items might appear on multiple pages or be skipped entirely.

Common pagination patterns

Pattern 1: Fetch all pages sequentially

The most common pattern—fetch pages one at a time until you've got everything.

Node.js:

async function getAllCases() {
  const allCases = [];
  let page = 0;
  let hasMore = true;

  while (hasMore) {
    const response = await getCases(page, 50); // 50 items per page

    // Add this page's cases to our collection
    allCases.push(...response.data);

    // Check if there are more pages
    hasMore = !response.pagination.last;
    page++;

    console.log(`Fetched page ${page}, total cases so far: ${allCases.length}`);
  }

  console.log(`Complete! Fetched ${allCases.length} cases across ${page} pages`);
  return allCases;
}

// Usage
const allCases = await getAllCases();
// Processes all cases: page 1, page 2, page 3, ... until done

Python:

def get_all_cases():
    all_cases = []
    page = 0
    has_more = True

    while has_more:
        response = get_cases(page, 50)  # 50 items per page

        # Add this page's cases
        all_cases.extend(response['data'])

        # Check if there are more pages
        has_more = not response['pagination']['last']
        page += 1

        print(f"Fetched page {page}, total cases so far: {len(all_cases)}")

    print(f"Complete! Fetched {len(all_cases)} cases across {page} pages")
    return all_cases

# Usage
all_cases = get_all_cases()

When to use this:

  • You need all the data for analysis or reporting
  • You're doing a one-time data export
  • The total dataset is manageable (under 10,000 items)

When NOT to use this:

  • Very large datasets (100,000+ items) - consider streaming instead
  • Real-time UI displays - use progressive loading instead
  • Rate limit concerns - fetch only what you need

Pattern 2: Process pages progressively

Instead of loading everything into memory, process each page as you fetch it.

Node.js:

async function processAllCases(processFn) {
  let page = 0;
  let hasMore = true;
  let totalProcessed = 0;

  while (hasMore) {
    const response = await getCases(page, 50);

    // Process this page immediately
    for (const case of response.data) {
      await processFn(case);
      totalProcessed++;
    }

    hasMore = !response.pagination.last;
    page++;

    console.log(`Processed page ${page}, ${totalProcessed} cases total`);
  }

  return totalProcessed;
}

// Usage - process each case as we fetch it
const processed = await processAllCases(async (caseItem) => {
  // Do something with each case
  await updateDatabase(caseItem);
  await sendToQueue(caseItem);
});

console.log(`Processed ${processed} cases`);

Python:

def process_all_cases(process_fn):
    page = 0
    has_more = True
    total_processed = 0

    while has_more:
        response = get_cases(page, 50)

        # Process this page immediately
        for case in response['data']:
            process_fn(case)
            total_processed += 1

        has_more = not response['pagination']['last']
        page += 1

        print(f"Processed page {page}, {total_processed} cases total")

    return total_processed

# Usage
def handle_case(case):
    # Do something with each case
    update_database(case)
    send_to_queue(case)

processed = process_all_cases(handle_case)
print(f"Processed {processed} cases")

Benefits:

  • Memory efficient - Never holds entire dataset in memory
  • Faster time-to-first-result - Start processing immediately
  • Better for large datasets - Can handle millions of records
  • Easier error recovery - If it fails on page 42, you've already processed pages 1-41

Pattern 3: Fetch a specific page

Sometimes you just need one page—like for a UI with page navigation.

Node.js:

async function getCasesForDisplay(pageNumber = 0, pageSize = 20) {
  const response = await getCases(pageNumber, pageSize);

  return {
    cases: response.data,
    pagination: {
      currentPage: response.pagination.page + 1, // Convert to 1-indexed for display
      totalPages: response.pagination.totalPages,
      totalItems: response.pagination.totalElements,
      hasNext: !response.pagination.last,
      hasPrevious: !response.pagination.first
    }
  };
}

// Usage in a UI
const displayData = await getCasesForDisplay(2, 20); // Page 3 (0-indexed: 2)

console.log(`Page ${displayData.pagination.currentPage} of ${displayData.pagination.totalPages}`);
console.log(`Showing ${displayData.cases.length} cases`);

if (displayData.pagination.hasNext) {
  console.log('Next button enabled');
}
if (displayData.pagination.hasPrevious) {
  console.log('Previous button enabled');
}

Pattern 4: Parallel page fetching

Fetch multiple pages at once for faster data retrieval.

Node.js:

async function getPageRange(startPage, endPage, pageSize = 20) {
  // Create array of page numbers: [0, 1, 2, 3, 4]
  const pages = Array.from(
    { length: endPage - startPage + 1 },
    (_, i) => startPage + i
  );

  // Fetch all pages in parallel
  const responses = await Promise.all(
    pages.map(page => getCases(page, pageSize))
  );

  // Combine all results
  const allCases = responses.flatMap(response => response.data);

  return allCases;
}

// Usage - fetch pages 0-4 in parallel (5 pages = 100 cases if size=20)
const cases = await getPageRange(0, 4, 20);
console.log(`Fetched ${cases.length} cases in parallel`);

⚠️ Important considerations:

  • Rate limits: Parallel requests count against your rate limit. Don't fetch 100 pages at once.
  • Memory: All pages load into memory simultaneously
  • Order: Results come back in any order—sort after fetching if order matters

Good practice:

// Fetch 5 pages in parallel (reasonable)
const batch1 = await getPageRange(0, 4, 20);

// Wait a moment, then fetch next batch
await new Promise(resolve => setTimeout(resolve, 1000));
const batch2 = await getPageRange(5, 9, 20);

Pattern 5: Infinite scroll / lazy loading

Load pages as the user scrolls or requests more data.

Node.js class for infinite scroll:

class InfiniteScroll {
  constructor(fetchFn, pageSize = 20) {
    this.fetchFn = fetchFn;
    this.pageSize = pageSize;
    this.currentPage = 0;
    this.allItems = [];
    this.hasMore = true;
    this.loading = false;
    this.totalItems = 0;
  }

  async loadMore() {
    // Prevent multiple simultaneous loads
    if (this.loading || !this.hasMore) {
      return [];
    }

    this.loading = true;

    try {
      const response = await this.fetchFn(this.currentPage, this.pageSize);

      // Add new items
      this.allItems.push(...response.data);

      // Update state
      this.hasMore = !response.pagination.last;
      this.totalItems = response.pagination.totalElements;
      this.currentPage++;

      console.log(`Loaded page ${this.currentPage}, showing ${this.allItems.length} of ${this.totalItems} total`);

      return response.data; // Return just the new items
    } finally {
      this.loading = false;
    }
  }

  getAllItems() {
    return this.allItems;
  }

  reset() {
    this.currentPage = 0;
    this.allItems = [];
    this.hasMore = true;
    this.totalItems = 0;
  }
}

// Usage in a web app
const scroll = new InfiniteScroll(getCases, 20);

// Initial load
const firstBatch = await scroll.loadMore();
displayCases(firstBatch);

// User scrolls down
document.addEventListener('scroll', async () => {
  const scrolledToBottom =
    window.innerHeight + window.scrollY >= document.body.offsetHeight - 100;

  if (scrolledToBottom && !scroll.loading) {
    const newItems = await scroll.loadMore();
    appendCases(newItems);
  }
});

Choosing the right page size

Page size affects performance, memory usage, and user experience.

Page size trade-offs

Page SizeProsConsBest For
Small (10-20)- Fast response times
- Low memory usage
- Quick time-to-first-render
- More API requests
- More overhead
- Higher rate limit usage
UI pagination, mobile apps
Medium (20-50)- Balanced performance
- Reasonable memory
- Fewer requests
- Slightly slower responsesMost use cases, dashboards
Large (50-100)- Fewer requests
- Lower rate limit usage
- Efficient for batch processing
- Slower responses
- More memory usage
- Longer wait for first result
Batch processing, exports
// UI pagination (user clicking through pages)
const uiCases = await getCases(page, 20);

// Infinite scroll (loading as user scrolls)
const scrollCases = await getCases(page, 30);

// Background processing (analyzing all cases)
const batchCases = await getCases(page, 100);

// Mobile app (limited bandwidth)
const mobileCases = await getCases(page, 15);

// Real-time dashboard (frequent updates)
const dashboardCases = await getCases(page, 25);

Performance comparison:

Fetching 1,000 cases:

Page size 10:  100 requests, ~30 seconds
Page size 20:   50 requests, ~15 seconds ✅ Good for UI
Page size 50:   20 requests,  ~6 seconds ✅ Good for batch
Page size 100:  10 requests,  ~4 seconds ✅ Fastest, but largest responses

Handling edge cases

Empty results

Always check if a page has any data:

async function getCasesWithEmptyCheck(page, size) {
  const response = await getCases(page, size);

  if (!response.data || response.data.length === 0) {
    if (page === 0) {
      console.log('No cases found at all');
    } else {
      console.log('Reached end of results');
    }
    return { isEmpty: true, data: [] };
  }

  return { isEmpty: false, data: response.data };
}

Page out of range

Handle requests for pages that don't exist:

async function getSafePage(page, size) {
  const response = await getCases(page, size);

  // Check if requested page is beyond available pages
  if (page >= response.pagination.totalPages) {
    console.warn(`Requested page ${page} but only ${response.pagination.totalPages} pages exist`);
    // Return last valid page instead
    return getCases(response.pagination.totalPages - 1, size);
  }

  return response;
}

Data changes during pagination

The dataset might change while you're fetching pages:

Problem:

// Fetch page 0 at 10:00 AM - 50 cases returned
const page1 = await getCases(0, 50);

// New cases are created at 10:01 AM

// Fetch page 1 at 10:02 AM - you might skip or duplicate items!
const page2 = await getCases(1, 50);

Solutions:

  1. Use timestamps to filter:
const startTime = new Date().toISOString();

// All requests use the same timestamp filter
const page1 = await getCases(0, 50, { createdBefore: startTime });
const page2 = await getCases(1, 50, { createdBefore: startTime });
// This ensures a consistent snapshot of data
  1. Use cursor-based pagination for real-time data (covered in advanced section)

  2. Fetch quickly to minimize window for changes:

// Fetch pages rapidly in parallel
const pages = await Promise.all([
  getCases(0, 50),
  getCases(1, 50),
  getCases(2, 50)
]);

Advanced: Cursor-based pagination

For datasets that change frequently, cursor-based pagination is more reliable than offset-based.

How cursor pagination works

Instead of page numbers, you use a cursor (a pointer to a specific item). The API returns the next cursor with each response.

Request 1: GET /cases?size=20
Response:  Items 1-20, nextCursor="abc123"

Request 2: GET /cases?size=20&cursor=abc123
Response:  Items 21-40, nextCursor="def456"

Request 3: GET /cases?size=20&cursor=def456
Response:  Items 41-60, nextCursor="ghi789"

Benefits over offset pagination:

  • No duplicate items even if data changes during pagination
  • No skipped items even if items are added/deleted
  • More efficient for databases to process

Drawbacks:

  • Can't jump to arbitrary pages (no "go to page 5")
  • Can't calculate total pages upfront

Implementing cursor pagination

async function iterateWithCursor(fetchFn, pageSize = 50) {
  let cursor = null;
  const allItems = [];

  do {
    const result = await fetchFn(cursor, pageSize);

    allItems.push(...result.items);
    cursor = result.nextCursor;

    console.log(`Fetched ${result.items.length} items, total so far: ${allItems.length}`);
  } while (cursor !== null);

  return allItems;
}

// Usage
const allCases = await iterateWithCursor(async (cursor, size) => {
  const params = new URLSearchParams({ size });
  if (cursor) params.append('cursor', cursor);

  const response = await fetch(
    `https://api-v3.sprinklr.com/api/v3/case/search?${params}`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Key': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ status: 'OPEN' })
    }
  );

  const data = await response.json();

  return {
    items: data.data,
    nextCursor: data.pagination?.nextCursor || null
  };
});

Caching paginated results

Caching can dramatically improve performance for frequently accessed pages.

Smart pagination cache

class PaginationCache {
  constructor(ttl = 300000) { // 5 minutes default
    this.cache = new Map();
    this.ttl = ttl;
  }

  generateKey(page, size, sort, filters) {
    return `${page}:${size}:${sort}:${JSON.stringify(filters)}`;
  }

  set(page, size, sort, filters, data) {
    const key = this.generateKey(page, size, sort, filters);
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }

  get(page, size, sort, filters) {
    const key = this.generateKey(page, size, sort, filters);
    const cached = this.cache.get(key);

    if (!cached) return null;

    // Check if expired
    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  clear() {
    this.cache.clear();
  }

  // Clear cache for specific filters (e.g., when data changes)
  clearMatching(filters) {
    const filterStr = JSON.stringify(filters);
    for (const [key, _] of this.cache) {
      if (key.includes(filterStr)) {
        this.cache.delete(key);
      }
    }
  }
}

// Usage
const cache = new PaginationCache(300000); // 5 minutes

async function getCasesWithCache(page, size, filters = {}) {
  const sort = 'createdTime,desc';

  // Check cache first
  const cached = cache.get(page, size, sort, filters);
  if (cached) {
    console.log('Cache hit!');
    return cached;
  }

  // Fetch from API
  console.log('Cache miss, fetching from API...');
  const response = await getCases(page, size);

  // Cache the result
  cache.set(page, size, sort, filters, response);

  return response;
}

// When a case is updated, clear relevant cache entries
function onCaseUpdated(caseId) {
  cache.clearMatching({ status: 'OPEN' });
  console.log('Cleared cache for OPEN cases');
}

Error handling in pagination

Robust error handling is essential for pagination since you're making multiple requests.

async function getAllCasesWithErrorHandling() {
  const allCases = [];
  let page = 0;
  let hasMore = true;
  const maxRetries = 3;

  while (hasMore) {
    let attempt = 0;
    let success = false;
    let response;

    // Retry logic for each page
    while (attempt < maxRetries && !success) {
      try {
        response = await getCases(page, 50);
        success = true;
      } catch (error) {
        attempt++;
        console.error(`Error fetching page ${page}, attempt ${attempt}:`, error.message);

        if (attempt >= maxRetries) {
          throw new Error(`Failed to fetch page ${page} after ${maxRetries} attempts`);
        }

        // Exponential backoff
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Waiting ${waitTime}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }

    if (!response.success) {
      console.error(`Page ${page} returned success=false`);
      break;
    }

    allCases.push(...response.data);
    hasMore = !response.pagination.last;
    page++;
  }

  return {
    cases: allCases,
    completedPages: page,
    complete: !hasMore
  };
}

// Usage
try {
  const result = await getAllCasesWithErrorHandling();

  if (result.complete) {
    console.log(`Successfully fetched all ${result.cases.length} cases`);
  } else {
    console.warn(`Partial fetch: got ${result.cases.length} cases from ${result.completedPages} pages`);
  }
} catch (error) {
  console.error('Pagination failed completely:', error);
}

Performance optimization tips

1. Fetch only the fields you need

If the API supports field selection, specify only required fields to reduce response size:

const response = await fetch(
  'https://api-v3.sprinklr.com/api/v3/case/search?page=0&size=50&fields=caseId,subject,status',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Key': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status: 'OPEN' })
  }
);

2. Use appropriate page sizes based on data volume

// Estimate total time for different strategies
function estimateFetchTime(totalItems, pageSize, requestTime = 300) {
  const totalPages = Math.ceil(totalItems / pageSize);
  const totalTime = totalPages * requestTime;
  return {
    totalPages,
    totalTime: `${(totalTime / 1000).toFixed(1)} seconds`,
    requestsPerMinute: Math.ceil((totalPages / totalTime) * 60000)
  };
}

// Compare strategies for 5000 items
console.log('Page size 20:', estimateFetchTime(5000, 20));  // 250 pages, 75 seconds
console.log('Page size 50:', estimateFetchTime(5000, 50));  // 100 pages, 30 seconds
console.log('Page size 100:', estimateFetchTime(5000, 100)); // 50 pages, 15 seconds

3. Parallel fetching with rate limit awareness

async function fetchPagesInParallel(totalPages, pageSize, concurrency = 3) {
  const allResults = [];

  // Process pages in batches to respect rate limits
  for (let batch = 0; batch < Math.ceil(totalPages / concurrency); batch++) {
    const startPage = batch * concurrency;
    const endPage = Math.min(startPage + concurrency, totalPages);

    const batchPromises = [];
    for (let page = startPage; page < endPage; page++) {
      batchPromises.push(getCases(page, pageSize));
    }

    const batchResults = await Promise.all(batchPromises);
    allResults.push(...batchResults);

    console.log(`Fetched batch ${batch + 1}, pages ${startPage}-${endPage - 1}`);

    // Small delay between batches
    if (endPage < totalPages) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }

  return allResults.flatMap(r => r.data);
}

// Fetch 30 pages, 3 at a time
const cases = await fetchPagesInParallel(30, 50, 3);

Troubleshooting pagination issues

Issue: Missing or duplicate items

Symptoms:

  • Items appear twice in your results
  • Items are missing when you fetch all pages
  • Total count doesn't match expected number

Causes:

  • Data changing between page fetches
  • Inconsistent sort order between requests
  • Starting page number from 1 instead of 0

Solutions:

// ✅ Always use 0-indexed pages
const correctPage = await getCases(0, 20); // First page

// ❌ Don't use 1-indexed
const wrongPage = await getCases(1, 20); // This is the SECOND page!

// ✅ Use consistent sort order
const page1 = await getCases(0, 20, 'createdTime,desc');
const page2 = await getCases(1, 20, 'createdTime,desc'); // Same sort!

// ✅ Use timestamp filters for consistent snapshots
const timestamp = new Date().toISOString();
const page1 = await getCases(0, 20, { createdBefore: timestamp });
const page2 = await getCases(1, 20, { createdBefore: timestamp });

Issue: Slow pagination performance

Symptoms:

  • Taking too long to fetch all pages
  • Requests timing out
  • Memory issues with large datasets

Solutions:

// 1. Increase page size to reduce total requests
const largerPages = await getCases(page, 100); // Max size

// 2. Use parallel fetching (but respect rate limits)
const parallel = await Promise.all([
  getCases(0, 50),
  getCases(1, 50),
  getCases(2, 50)
]);

// 3. Process progressively instead of loading all at once
await processAllCases(async (caseItem) => {
  // Process each case immediately
  await handleCase(caseItem);
});

// 4. Use appropriate caching
cache.set(page, size, sort, filters, response);

Issue: Page count changes during iteration

Symptoms:

  • totalPages is different on each request
  • Can't reach the last page
  • Infinite loop in pagination

Causes:

  • Items being added/deleted during pagination
  • Filters changing between requests

Solutions:

// Use last flag instead of totalPages
async function getAllCasesSafely() {
  const allCases = [];
  let page = 0;
  let hasMore = true;
  const maxPages = 1000; // Safety limit

  while (hasMore && page < maxPages) {
    const response = await getCases(page, 50);
    allCases.push(...response.data);

    // Use 'last' flag, not totalPages
    hasMore = !response.pagination.last;
    page++;
  }

  if (page >= maxPages) {
    console.warn('Hit max page limit, there may be more data');
  }

  return allCases;
}

Next steps

Now that you understand pagination, explore these related topics:

  • API Basics - Learn about batch operations and other core API patterns
  • Rate Limiting - Optimize pagination performance with rate limit strategies
  • Error Handling - Handle pagination errors gracefully
  • Best Practices - Production-ready patterns for API integrations

Need help?

If you're experiencing pagination issues:

  1. Verify your page numbers - Remember, pages start at 0, not 1
  2. Check your sort order - Inconsistent sorting causes duplicate/missing items
  3. Review response metadata - The pagination object tells you what's happening
  4. Monitor performance - Track fetch times and adjust page sizes

Support resources:

  • Technical support: support.sprinklr.com
  • API documentation: This guide and related docs
  • Community forums: Share pagination patterns with other developers