Skip to main content

Overview

All list endpoints use cursor-based pagination. This is more efficient than offset pagination for large datasets and handles insertions/deletions gracefully.

Parameters

ParameterTypeDefaultDescription
limitinteger50Number of items per page (1-100)
cursorstring-Cursor from previous response

Basic Usage

First Request

GET /products?limit=25
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)

TypeScript
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);
}

Filtering with Pagination

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.

Endpoints Supporting Pagination

EndpointDefault LimitMax Limit
GET /products50100
GET /invoices50100
GET /customers50100
GET /coupons50100
GET /subscriptions50100
GET /reviews50100
GET /tickets50100
GET /webhooks50100
GET /blacklist50100
GET /whitelist50100

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.