> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.beehiiv.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.beehiiv.com/_mcp/server.

# 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

| Parameter | Type    | Description                                      |
| --------- | ------- | ------------------------------------------------ |
| `cursor`  | string  | Opaque cursor token for pagination position      |
| `limit`   | integer | Number of results to return (1-100, default: 10) |

### Response Format

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

### Example Usage

**First page:**

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

**Next page:**

```bash
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

> **Info**
>
> 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)

> **Warning**
>
> 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

| Parameter | Type    | Description                                     |
| --------- | ------- | ----------------------------------------------- |
| `page`    | integer | Page number (1-100)                             |
| `limit`   | integer | Number of results per page (1-100, default: 10) |

### Response Format

```json
{
  "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:

```diff
- 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:

```diff
// 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

```javascript
// 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

| Feature           | Offset Pagination        | Cursor Pagination                    |
| ----------------- | ------------------------ | ------------------------------------ |
| **Page limit**    | 100 pages max            | Unlimited                            |
| **Performance**   | Degrades with depth      | Consistent                           |
| **Consistency**   | May have gaps/duplicates | Always consistent                    |
| **Total count**   | Always included          | Not included (performance optimized) |
| **Random access** | Yes (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

```javascript
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](/api-reference) for endpoint-specific examples
* Review the [Rate Limiting](/welcome/rate-limiting) documentation
* Contact support if you encounter issues during migration