Pagination

The beehiiv API uses pagination to manage large result sets efficiently. We support two pagination methods:

  1. Cursor-based pagination (recommended)
  2. Offset-based pagination (deprecated)

Cursor-Based Pagination

Cursor-based pagination provides consistent, efficient navigation through large datasets. It uses opaque cursor tokens to mark positions in the result set.

How It Works

  • Use the cursor parameter to specify where to start fetching results
  • Set limit to control how many results to return per page (max 100)
  • The response includes a next_cursor for fetching the next page
  • Use has_more to determine if additional pages exist

Request Parameters

ParameterTypeDescription
cursorstringOpaque cursor token for pagination position
limitintegerNumber of results to return (1-100, default: 10)

Response Format

{
"data": [...],
"pagination": {
"limit": 10,
"has_more": true,
"next_cursor": "eyJ0aW1lc3RhbXAiOiIyMDI0LTA3LTAyVDE3OjMwOjAwLjAwMDAwMFoifQ=="
}
}

Example Usage

First page:

GET /v2/publications/pub_123/subscriptions?limit=10

Next page:

GET /v2/publications/pub_123/subscriptions?limit=10&cursor=eyJ0aW1lc3RhbXAiOiIyMDI0LTA3LTAyVDE3OjMwOjAwLjAwMDAwMFoifQ==

Advantages

  • Consistent results: No duplicates or missing items during pagination
  • Performance: Efficient for large datasets
  • Real-time friendly: Works well when data changes frequently
  • No deep pagination issues: Maintains performance regardless of page depth

Cursor-based pagination is optimized for performance and does not include total counts. Use the has_more field to determine if additional pages exist.

Offset-Based Pagination (Deprecated)

Offset-based pagination is deprecated and will be removed in a future API version. Please migrate to cursor-based pagination.

Offset-based pagination uses page numbers and limits. While still supported, it has several limitations:

  • Page limit: Requests beyond page 100 are blocked
  • Consistency issues: Results may shift when data changes
  • Performance degradation: Slower for deep pagination

Request Parameters

ParameterTypeDescription
pageintegerPage number (1-100)
limitintegerNumber of results per page (1-100, default: 10)

Response Format

{
"data": [...],
"pagination": {
"page": 1,
"limit": 10,
"total_results": 1500,
"total_pages": 150
}
}

Deprecation Timeline

  • Current: All offset pagination requests return deprecation warning headers
  • After page 100: Requests return a 400 error with migration guidance
  • Future: Complete removal of offset pagination support

Migration Guide

Step 1: Update Your Code

Replace offset pagination parameters:

- GET /v2/publications/pub_123/subscriptions?page=1&limit=10
+ GET /v2/publications/pub_123/subscriptions?limit=10

Step 2: Handle Response Changes

Update your response parsing:

// Old offset pagination
- if (response.pagination.page < response.pagination.total_pages) {
- fetchNextPage(response.pagination.page + 1);
- }
// New cursor pagination
+ if (response.pagination.has_more) {
+ fetchNextPage(response.pagination.next_cursor);
+ }

Step 3: Update Pagination Logic

// Cursor-based pagination example
async function getAllSubscriptions(publicationId, limit = 10) {
const allSubscriptions = [];
let cursor = null;
let hasMore = true;
while (hasMore) {
const params = new URLSearchParams({ limit: limit.toString() });
if (cursor) {
params.append('cursor', cursor);
}
const response = await fetch(
`/v2/publications/${publicationId}/subscriptions?${params}`
);
const data = await response.json();
allSubscriptions.push(...data.data);
hasMore = data.pagination.has_more;
cursor = data.pagination.next_cursor;
}
return allSubscriptions;
}

Key Differences

FeatureOffset PaginationCursor Pagination
Page limit100 pages maxUnlimited
PerformanceDegrades with depthConsistent
ConsistencyMay have gaps/duplicatesAlways consistent
Total countAlways includedNot included (performance optimized)
Random accessYes (page number)No (sequential only)

Backward Compatibility

During the transition period:

  • Both pagination methods are supported
  • Offset requests include deprecation warning headers:
    • X-Pagination-Warning: Deprecation notice
    • X-Pagination-Migration-Guide: Link to this documentation
  • Requests beyond page 100 return a 400 error

Testing Your Migration

  1. Test cursor pagination: Verify your code handles cursor tokens correctly
  2. Check error handling: Ensure graceful handling of pagination errors
  3. Performance testing: Compare performance of cursor vs offset pagination
  4. Monitor headers: Watch for deprecation warnings in your logs

Best Practices

Cursor Pagination

  • Store cursors securely: Treat cursor tokens as opaque strings
  • Handle missing cursors: Start from the beginning if cursor is invalid
  • Use appropriate limits: Balance between API calls and memory usage
  • Cache strategically: Cache results but not cursor tokens

Error Handling

try {
const response = await fetch(endpoint);
if (response.status === 400) {
// Handle pagination error (e.g., invalid cursor)
console.error('Pagination error:', await response.json());
// Restart from beginning
return fetchFromBeginning();
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
throw error;
}

Rate Limiting

Be mindful of rate limits when implementing pagination:

  • Respect rate limits: Don’t exceed API rate limits
  • Implement backoff: Use exponential backoff for rate limit errors
  • Batch wisely: Use appropriate page sizes to minimize requests

Support

If you need help migrating to cursor-based pagination:

  • Check our API Reference for endpoint-specific examples
  • Review the Rate Limiting documentation
  • Contact support if you encounter issues during migration