Overview
All list endpoints use cursor-based pagination . This is more efficient than offset pagination for large datasets and handles insertions/deletions gracefully.
Parameters
Parameter Type Default Description limitinteger 50 Number of items per page (1-100) cursorstring - Cursor from previous response
Basic Usage
First Request
Response:
{
"data" : [
{ "uniqid" : "prod_001" , "title" : "Product A" },
{ "uniqid" : "prod_002" , "title" : "Product B" },
// ... 23 more items
],
"pagination" : {
"next_cursor" : "eyJpZCI6MjUsImNyZWF0ZWRfYXQiOiIyMDI0LTAxLTAxIn0" ,
"has_more" : true
}
}
Next Page
Use the next_cursor from the previous response:
GET /products?limit= 25 & cursor = eyJpZCI6MjUsImNyZWF0ZWRfYXQiOiIyMDI0LTAxLTAxIn0
Final Page
When there are no more items:
{
"data" : [
{ "uniqid" : "prod_099" , "title" : "Product Y" },
{ "uniqid" : "prod_100" , "title" : "Product Z" }
],
"pagination" : {
"next_cursor" : null ,
"has_more" : false
}
}
Code Examples
Fetch All Pages
async function fetchAllProducts () : Promise < Product []> {
const allProducts : Product [] = [];
let cursor : string | null = null ;
do {
const url = new URL ( 'https://api.shoppex.io/dev/v1/products' );
url . searchParams . set ( 'limit' , '100' );
if ( cursor ) url . searchParams . set ( 'cursor' , cursor );
const response = await fetch ( url . toString (), {
headers: { 'Authorization' : `Bearer ${ API_KEY } ` }
});
const { data , pagination } = await response . json ();
allProducts . push ( ... data );
cursor = pagination . next_cursor ;
} while ( cursor );
return allProducts ;
}
Async Generator (Streaming)
async function* fetchProductsStream () : AsyncGenerator < Product > {
let cursor : string | null = null ;
do {
const url = new URL ( 'https://api.shoppex.io/dev/v1/products' );
url . searchParams . set ( 'limit' , '100' );
if ( cursor ) url . searchParams . set ( 'cursor' , cursor );
const response = await fetch ( url . toString (), {
headers: { 'Authorization' : `Bearer ${ API_KEY } ` }
});
const { data , pagination } = await response . json ();
for ( const product of data ) {
yield product ;
}
cursor = pagination . next_cursor ;
} while ( cursor );
}
// Usage
for await ( const product of fetchProductsStream ()) {
console . log ( product . title );
}
Filters work alongside pagination:
# Get completed invoices, paginated
GET /invoices?status=COMPLETED & limit = 50
# Continue with cursor
GET /invoices?status=COMPLETED & limit = 50 & cursor = eyJ...
Always include the same filters when following cursors. Changing filters invalidates the cursor.
Endpoint Default Limit Max Limit GET /products50 100 GET /invoices50 100 GET /customers50 100 GET /coupons50 100 GET /subscriptions50 100 GET /reviews50 100 GET /tickets50 100 GET /webhooks50 100 GET /blacklist50 100 GET /whitelist50 100
Best Practices
Use Reasonable Limits Start with 50-100 items. Larger limits increase response time.
Don't Store Cursors Cursors are meant for immediate use. They may expire or become invalid.
Handle Empty Results An empty data array with has_more: false is valid - no items match your query.
Respect Rate Limits When fetching all pages, add delays between requests to avoid rate limiting.