> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoppex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Navigate Dev API result sets with cursor-based and page-based pagination

## Overview

The Dev API currently uses **two pagination models**:

* **Cursor-based pagination** for most resource lists
* **Page-based pagination** for operational lists where page navigation is clearer

Most resource lists (products, invoices, orders) use cursor-based. Operational lists like webhook delivery logs use page-based.

***

<Tabs>
  <Tab title="Cursor-Based">
    Use cursor-based pagination for feed-style lists like products, invoices, orders, customers, and subscriptions.

    ### Parameters

    | Parameter | Type    | Default | Description                      |
    | --------- | ------- | ------- | -------------------------------- |
    | `limit`   | integer | 50      | Number of items per page (1-100) |
    | `cursor`  | string  | -       | Cursor from previous response    |

    ***

    ### Basic Usage

    #### First Request

    ```bash theme={"system"}
    GET /products?limit=25
    ```

    **Response:**

    ```json theme={"system"}
    {
      "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:

    ```bash theme={"system"}
    GET /products?limit=25&cursor=eyJpZCI6MjUsImNyZWF0ZWRfYXQiOiIyMDI0LTAxLTAxIn0
    ```

    #### Final Page

    When there are no more items:

    ```json theme={"system"}
    {
      "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

    <CodeGroup>
      ```typescript TypeScript theme={"system"}
      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;
      }
      ```

      ```python Python theme={"system"}
      def fetch_all_products():
          all_products = []
          cursor = None

          while True:
              params = {'limit': 100}
              if cursor:
                  params['cursor'] = cursor

              response = requests.get(
                  'https://api.shoppex.io/dev/v1/products',
                  headers={'Authorization': f'Bearer {API_KEY}'},
                  params=params
              )

              result = response.json()
              all_products.extend(result['data'])

              if not result['pagination']['has_more']:
                  break

              cursor = result['pagination']['next_cursor']

          return all_products
      ```
    </CodeGroup>

    #### Async Generator (Streaming)

    ```typescript TypeScript theme={"system"}
    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:

    ```bash theme={"system"}
    # Get completed invoices, paginated
    GET /invoices?filters=status:COMPLETED&limit=50

    # Continue with cursor
    GET /invoices?filters=status:COMPLETED&limit=50&cursor=eyJ...
    ```

    <Warning>
      Always include the same filters and sorts when following cursors. Changing them invalidates the cursor.
    </Warning>

    ```bash theme={"system"}
    GET /orders?filters=status:COMPLETED,customer_email:buyer@example.com&sorts=-created_at
    GET /customers?filters=city:Berlin&sorts=created_at
    ```

    ***

    ### Cursor-Based Endpoints

    | Endpoint             | Default Limit | Max Limit |
    | -------------------- | ------------- | --------- |
    | `GET /products`      | 50            | 100       |
    | `GET /invoices`      | 50            | 100       |
    | `GET /customers`     | 50            | 100       |
    | `GET /coupons`       | 50            | 100       |
    | `GET /subscriptions` | 50            | 100       |
    | `GET /reviews`       | 50            | 100       |
    | `GET /tickets`       | 50            | 100       |
    | `GET /webhooks`      | 50            | 100       |
    | `GET /blacklist`     | 50            | 100       |

    ***
  </Tab>

  <Tab title="Page-Based">
    Use page-based pagination for operational lists where integrators often jump to a specific page.

    ### Parameters

    | Parameter | Type    | Default | Description                |
    | --------- | ------- | ------- | -------------------------- |
    | `page`    | integer | 1       | Page number, starting at 1 |
    | `limit`   | integer | 25      | Number of items per page   |

    ### Basic Usage

    ```bash theme={"system"}
    GET /webhooks/logs?page=2&limit=10
    ```

    ```json theme={"system"}
    {
      "data": [
        { "uniqid": "log_001", "status": "success" }
      ],
      "pagination": {
        "total": 37,
        "page": 2,
        "limit": 10,
        "total_pages": 4,
        "has_more": true
      }
    }
    ```

    ### Page-Based Endpoints

    | Endpoint                       | Default Limit |
    | ------------------------------ | ------------- |
    | `GET /webhooks/logs`           | 25            |
    | `GET /affiliates/customers`    | 25            |
    | `GET /affiliates/applications` | 25            |

    ***
  </Tab>
</Tabs>

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Reasonable Limits" icon="gauge">
    Fetching for a UI? 20-25 items is plenty. Background sync job? Crank it to 100. Larger limits increase response time.
  </Card>

  <Card title="Don't Store Cursors" icon="clock">
    Cursors are meant for immediate sequential use. They expire after 24 hours and become invalid if the underlying data changes significantly.
  </Card>

  <Card title="Handle Empty Results" icon="inbox">
    An empty `data` array with `has_more: false` is valid - no items match your query.
  </Card>

  <Card title="Respect Rate Limits" icon="shield">
    When fetching all pages, add delays between requests to avoid rate limiting.
  </Card>
</CardGroup>
