Skip to content

Pagination

The SellerCockpit API supports pagination for all list endpoints. You can choose between cursor-based pagination (recommended) or offset-based pagination depending on your use case.

Pagination Methods

MethodBest ForProsCons
Cursor-basedReal-time data, large datasetsConsistent results, efficientCan’t jump to specific page
Offset-basedSmaller datasets, page numbersSimple, can jump to any pageLess efficient, inconsistent with changes

Default Limits

  • Default page size: 50 items
  • Maximum page size: 100 items
  • Total count: Always included in the pagination object

Cursor-based pagination uses opaque tokens to fetch the next set of results. This is the recommended approach for most use cases.

How It Works

  1. Make an initial request (no cursor)
  2. API returns results + next_cursor
  3. Use next_cursor to fetch the next page
  4. Repeat until has_more is false

Request Parameters

ParameterTypeDefaultDescription
cursorstring-Cursor from previous response
limitnumber50Number of items per page (max: 100)

Response Format

{
"data": [
{ "id": "1", "name": "Contact 1" },
{ "id": "2", "name": "Contact 2" }
],
"pagination": {
"total": 150,
"limit": 50,
"cursor": "eyJpZCI6IjEiLCJjcmVhdGVkX2F0IjoiMjAyNC0wMS0xMFQxMDowMDowMFoifQ==",
"next_cursor": "eyJpZCI6IjUwIiwiY3JlYXRlZF9hdCI6IjIwMjQtMDEtMDlUMTU6MzA6MDBaIn0=",
"has_more": true
}
}

Example: Fetching All Contacts

Page 1 (Initial Request):

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?limit=50" \
-H "Authorization: Bearer sellercockpit_live_..."

Response:

{
"data": [
{ "id": "550e8400-...", "name": "Alice Johnson" },
{ "id": "660e8400-...", "name": "Bob Smith" }
// ... 48 more items
],
"pagination": {
"total": 150,
"limit": 50,
"next_cursor": "eyJpZCI6IjUwIiwi...",
"has_more": true
}
}

Page 2 (Using next_cursor):

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?limit=50&cursor=eyJpZCI6IjUwIiwi..." \
-H "Authorization: Bearer sellercockpit_live_..."

Response:

{
"data": [
{ "id": "770e8400-...", "name": "Charlie Brown" }
// ... 49 more items
],
"pagination": {
"total": 150,
"limit": 50,
"cursor": "eyJpZCI6IjUwIiwi...",
"next_cursor": "eyJpZCI6IjEwMCIs...",
"has_more": true
}
}

Last Page:

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?limit=50&cursor=eyJpZCI6IjEwMCIs..." \
-H "Authorization: Bearer sellercockpit_live_..."

Response:

{
"data": [
{ "id": "880e8400-...", "name": "Diana Prince" }
// ... only 49 items (last page)
],
"pagination": {
"total": 150,
"limit": 50,
"cursor": "eyJpZCI6IjEwMCIs...",
"next_cursor": null,
"has_more": false
}
}

JavaScript Implementation

Fetch all pages:

async function fetchAllContacts(apiKey: string) {
const baseUrl = 'https://your-project.supabase.co/functions/v1/api-v1-contacts';
const allContacts = [];
let cursor = null;
do {
const url = cursor
? `${baseUrl}?cursor=${cursor}&limit=100`
: `${baseUrl}?limit=100`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
const result = await response.json();
allContacts.push(...result.data);
cursor = result.pagination.next_cursor;
} while (cursor !== null);
return allContacts;
}

Async generator for lazy loading:

async function* contactsPaginator(apiKey: string) {
const baseUrl = 'https://your-project.supabase.co/functions/v1/api-v1-contacts';
let cursor = null;
do {
const url = cursor
? `${baseUrl}?cursor=${cursor}&limit=100`
: `${baseUrl}?limit=100`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
const result = await response.json();
yield result.data;
cursor = result.pagination.next_cursor;
} while (cursor !== null);
}
// Usage
for await (const page of contactsPaginator(apiKey)) {
console.log(`Fetched ${page.length} contacts`);
// Process page
}

Python Implementation

import requests
def fetch_all_contacts(api_key: str, base_url: str):
all_contacts = []
cursor = None
while True:
url = f"{base_url}?limit=100"
if cursor:
url += f"&cursor={cursor}"
response = requests.get(url, headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
})
result = response.json()
all_contacts.extend(result['data'])
cursor = result['pagination'].get('next_cursor')
if not cursor:
break
return all_contacts

Offset-Based Pagination

Offset-based pagination uses page numbers and page sizes. This is simpler but less efficient for large datasets.

Request Parameters

ParameterTypeDefaultDescription
pagenumber1Page number (1-indexed)
per_pagenumber50Items per page (max: 100)

Response Format

{
"data": [
{ "id": "1", "name": "Contact 1" },
{ "id": "2", "name": "Contact 2" }
],
"pagination": {
"total": 150,
"page": 1,
"per_page": 50,
"total_pages": 3,
"has_more": true,
"limit": 50
}
}

Example Requests

Page 1:

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?page=1&per_page=50" \
-H "Authorization: Bearer sellercockpit_live_..."

Page 2:

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?page=2&per_page=50" \
-H "Authorization: Bearer sellercockpit_live_..."

Page 3:

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?page=3&per_page=50" \
-H "Authorization: Bearer sellercockpit_live_..."

JavaScript Implementation

async function fetchContactsPage(apiKey: string, page: number, perPage: number = 50) {
const url = `https://your-project.supabase.co/functions/v1/api-v1-contacts?page=${page}&per_page=${perPage}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
return response.json();
}
// Fetch all pages
async function fetchAllContactsOffset(apiKey: string) {
const allContacts = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const result = await fetchContactsPage(apiKey, page, 100);
allContacts.push(...result.data);
hasMore = result.pagination.has_more;
page++;
}
return allContacts;
}

Python Implementation

def fetch_contacts_page(api_key: str, base_url: str, page: int, per_page: int = 50):
url = f"{base_url}?page={page}&per_page={per_page}"
response = requests.get(url, headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
})
return response.json()
# Fetch all pages
def fetch_all_contacts_offset(api_key: str, base_url: str):
all_contacts = []
page = 1
while True:
result = fetch_contacts_page(api_key, base_url, page, 100)
all_contacts.extend(result['data'])
if not result['pagination']['has_more']:
break
page += 1
return all_contacts

Combining with Filters and Sorting

Pagination works seamlessly with filters and sorting:

With Filters

Terminal window
# Cursor-based
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?organization_id=abc123&limit=50" \
-H "Authorization: Bearer sellercockpit_live_..."
# Offset-based
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?organization_id=abc123&page=1&per_page=50" \
-H "Authorization: Bearer sellercockpit_live_..."

With Sorting

Terminal window
# Sort by name ascending
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?sort=name&limit=50" \
-H "Authorization: Bearer sellercockpit_live_..."
# Sort by created_at descending
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?sort=-created_at&limit=50" \
-H "Authorization: Bearer sellercockpit_live_..."

Complex Example

Terminal window
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?organization_id=abc123&sort=-created_at&limit=25&cursor=eyJpZCI..." \
-H "Authorization: Bearer sellercockpit_live_..."

Best Practices

1. Use Cursor-Based for Large Datasets

Cursor-based pagination is more efficient and consistent:

// ✅ Cursor-based (recommended)
async function syncContacts(apiKey: string) {
let cursor = null;
do {
const result = await fetchPage(cursor);
await processContacts(result.data);
cursor = result.pagination.next_cursor;
} while (cursor);
}

2. Set Appropriate Page Sizes

Larger pages = fewer requests, but:

  • Balance between efficiency and memory usage
  • Consider network latency
  • Respect rate limits
// ❌ Too small (many requests)
const result = await fetch(`${url}?limit=10`);
// ❌ Too large (may timeout)
const result = await fetch(`${url}?limit=1000`); // Max is 100
// ✅ Just right
const result = await fetch(`${url}?limit=100`);

3. Handle Errors Gracefully

Always handle pagination errors:

async function robustPagination(apiKey: string) {
let cursor = null;
let retries = 0;
const maxRetries = 3;
do {
try {
const result = await fetchPage(cursor);
await processContacts(result.data);
cursor = result.pagination.next_cursor;
retries = 0; // Reset on success
} catch (error) {
if (retries >= maxRetries) {
throw new Error(`Failed after ${maxRetries} retries`);
}
retries++;
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
}
} while (cursor);
}

4. Cache Total Counts

The total count is expensive to calculate. Cache it when possible:

let cachedTotal = null;
let cacheTime = null;
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getTotalContacts(apiKey: string) {
if (cachedTotal && Date.now() - cacheTime < CACHE_TTL) {
return cachedTotal;
}
const result = await fetch(`${url}?limit=1`);
cachedTotal = result.pagination.total;
cacheTime = Date.now();
return cachedTotal;
}

5. Show Progress for Long Operations

Provide feedback when fetching many pages:

async function fetchWithProgress(apiKey: string, onProgress: (percent: number) => void) {
let cursor = null;
let fetched = 0;
// Get total count first
const firstPage = await fetchPage(null);
const total = firstPage.pagination.total;
do {
const result = await fetchPage(cursor);
fetched += result.data.length;
onProgress((fetched / total) * 100);
cursor = result.pagination.next_cursor;
} while (cursor);
}
// Usage
await fetchWithProgress(apiKey, (percent) => {
console.log(`Progress: ${percent.toFixed(1)}%`);
});

Common Patterns

Load More Button (Infinite Scroll)

class ContactList {
private cursor: string | null = null;
private contacts: Contact[] = [];
async loadMore() {
const url = this.cursor
? `${baseUrl}?cursor=${this.cursor}&limit=50`
: `${baseUrl}?limit=50`;
const result = await fetch(url, { headers });
const data = await result.json();
this.contacts.push(...data.data);
this.cursor = data.pagination.next_cursor;
return {
contacts: this.contacts,
hasMore: data.pagination.has_more
};
}
}
class PaginatedContactList {
private currentPage = 1;
private perPage = 50;
async goToPage(page: number) {
const url = `${baseUrl}?page=${page}&per_page=${this.perPage}`;
const result = await fetch(url, { headers });
const data = await result.json();
this.currentPage = page;
return {
contacts: data.data,
totalPages: data.pagination.total_pages,
currentPage: this.currentPage
};
}
async nextPage() {
return this.goToPage(this.currentPage + 1);
}
async prevPage() {
return this.goToPage(Math.max(1, this.currentPage - 1));
}
}

Performance Considerations

Cursor vs Offset Performance

Cursor-based (Faster):

-- Efficient: Uses index on (created_at, id)
SELECT * FROM contacts
WHERE (created_at, id) < ('2024-01-10', 'abc123')
ORDER BY created_at DESC, id DESC
LIMIT 50;

Offset-based (Slower for large offsets):

-- Inefficient: Must scan 1000 rows to skip them
SELECT * FROM contacts
ORDER BY created_at DESC
LIMIT 50 OFFSET 1000;

Recommendation

  • Small datasets (<10,000 records): Either method is fine
  • Large datasets (>10,000 records): Use cursor-based pagination
  • Need page numbers: Use offset-based for UI, but limit max pages

Next Steps