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
| Method | Best For | Pros | Cons |
|---|---|---|---|
| Cursor-based | Real-time data, large datasets | Consistent results, efficient | Can’t jump to specific page |
| Offset-based | Smaller datasets, page numbers | Simple, can jump to any page | Less efficient, inconsistent with changes |
Default Limits
- Default page size: 50 items
- Maximum page size: 100 items
- Total count: Always included in the
paginationobject
Cursor-Based Pagination (Recommended)
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
- Make an initial request (no cursor)
- API returns results +
next_cursor - Use
next_cursorto fetch the next page - Repeat until
has_moreisfalse
Request Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | - | Cursor from previous response |
limit | number | 50 | Number 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):
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):
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:
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);}
// Usagefor 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_contactsOffset-Based Pagination
Offset-based pagination uses page numbers and page sizes. This is simpler but less efficient for large datasets.
Request Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed) |
per_page | number | 50 | Items 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:
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?page=1&per_page=50" \ -H "Authorization: Bearer sellercockpit_live_..."Page 2:
curl "https://your-project.supabase.co/functions/v1/api-v1-contacts?page=2&per_page=50" \ -H "Authorization: Bearer sellercockpit_live_..."Page 3:
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 pagesasync 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 pagesdef 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_contactsCombining with Filters and Sorting
Pagination works seamlessly with filters and sorting:
With Filters
# Cursor-basedcurl "https://your-project.supabase.co/functions/v1/api-v1-contacts?organization_id=abc123&limit=50" \ -H "Authorization: Bearer sellercockpit_live_..."
# Offset-basedcurl "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
# Sort by name ascendingcurl "https://your-project.supabase.co/functions/v1/api-v1-contacts?sort=name&limit=50" \ -H "Authorization: Bearer sellercockpit_live_..."
# Sort by created_at descendingcurl "https://your-project.supabase.co/functions/v1/api-v1-contacts?sort=-created_at&limit=50" \ -H "Authorization: Bearer sellercockpit_live_..."Complex Example
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 rightconst 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);}
// Usageawait 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 }; }}Page Navigation (Traditional)
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 contactsWHERE (created_at, id) < ('2024-01-10', 'abc123')ORDER BY created_at DESC, id DESCLIMIT 50;Offset-based (Slower for large offsets):
-- Inefficient: Must scan 1000 rows to skip themSELECT * FROM contactsORDER BY created_at DESCLIMIT 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