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

Default response (optimal performance):

1{
2 "data": [...],
3 "pagination": {
4 "limit": 10,
5 "has_more": true,
6 "next_cursor": "eyJ0aW1lc3RhbXAiOiIyMDI0LTA3LTAyVDE3OjMwOjAwLjAwMDAwMFoifQ=="
7 }
8}

With total count (include_total=true):

1{
2 "data": [...],
3 "pagination": {
4 "limit": 10,
5 "has_more": true,
6 "next_cursor": "eyJ0aW1lc3RhbXAiOiIyMDI0LTA3LTAyVDE3OjMwOjAwLjAwMDAwMFoifQ==",
7 "total_results": 1500
8 }
9}

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

Total Results (Optional)

By default, cursor-based pagination doesn’t include total counts for optimal performance. You can optionally request total counts:

Request with total count:

$GET /v2/publications/pub_123/subscriptions?limit=10&include_total=true

Performance considerations:

  • include_total=false (default): Optimal performance, no additional database queries
  • include_total=true: Includes total count but requires an additional COUNT query

Use include_total=true only when you need to display total counts in your UI (e.g., “Showing 10 of 1,500 results”). For most pagination use cases, the has_more field is sufficient.

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

1{
2 "data": [...],
3 "pagination": {
4 "page": 1,
5 "limit": 10,
6 "total_results": 1500,
7 "total_pages": 150
8 }
9}

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:

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

Step 2: Handle Response Changes

Update your response parsing:

1// Old offset pagination
2- if (response.pagination.page < response.pagination.total_pages) {
3- fetchNextPage(response.pagination.page + 1);
4- }
5
6// New cursor pagination
7+ if (response.pagination.has_more) {
8+ fetchNextPage(response.pagination.next_cursor);
9+ }

Step 3: Update Pagination Logic

1// Cursor-based pagination example
2async function getAllSubscriptions(publicationId, limit = 10) {
3 const allSubscriptions = [];
4 let cursor = null;
5 let hasMore = true;
6
7 while (hasMore) {
8 const params = new URLSearchParams({ limit: limit.toString() });
9 if (cursor) {
10 params.append('cursor', cursor);
11 }
12
13 const response = await fetch(
14 `/v2/publications/${publicationId}/subscriptions?${params}`
15 );
16 const data = await response.json();
17
18 allSubscriptions.push(...data.data);
19
20 hasMore = data.pagination.has_more;
21 cursor = data.pagination.next_cursor;
22 }
23
24 return allSubscriptions;
25}

Key Differences

FeatureOffset PaginationCursor Pagination
Page limit100 pages maxUnlimited
PerformanceDegrades with depthConsistent
ConsistencyMay have gaps/duplicatesAlways consistent
Total countAlways includedOptional (include_total=true)
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

1try {
2 const response = await fetch(endpoint);
3
4 if (response.status === 400) {
5 // Handle pagination error (e.g., invalid cursor)
6 console.error('Pagination error:', await response.json());
7 // Restart from beginning
8 return fetchFromBeginning();
9 }
10
11 return await response.json();
12} catch (error) {
13 console.error('Request failed:', error);
14 throw error;
15}

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