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

# Pagination and filtering

> Page through collections with cursor pagination and narrow results with documented filters.

List endpoints return a page of results plus links to walk forwards and backwards.

## Envelope

Every list response has the same shape:

```json theme={null}
{
  "data": [ /* resources */ ],
  "links": {
    "next": "https://your-shop.example/api/partner/v1/orders?cursor=...",
    "prev": null
  },
  "meta": {
    "per_page": 25,
    "has_more": true
  }
}
```

* `data` holds the resources for this page.
* `links.next` and `links.prev` are ready-to-use URLs, or `null` at the ends.
* `meta.has_more` tells you whether another page exists.

## Paging

The `cursor` query parameter is an opaque bookmark. It encodes where the previous page ended, so the next request can continue from exactly that record. The API generates it for you and includes it in `links.next`; you never need to read, build, or modify it. If you have integrated with Stripe or Shopify this is the same pattern as `starting_after` or `page_info`.

Cursors are used instead of page numbers because they stay stable while data changes. With page numbers, a record created while you walk the list shifts every following page and you see duplicates or gaps. A cursor pins your position to the last record you saw, so inserts and updates elsewhere in the list cannot move you.

To integrate, treat paging as a loop:

1. Request the first page with your filters and `per_page`.
2. Process `data`.
3. If `links.next` is not `null`, request it as-is (it already carries your filters and page size).
4. Stop when `links.next` is `null` or `meta.has_more` is `false`.

```javascript theme={null}
let url = 'https://your-shop.example/api/partner/v1/orders?per_page=50';

while (url) {
  const page = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  }).then((response) => response.json());

  for (const order of page.data) {
    // process each order
  }

  url = page.links.next;
}
```

Set the page size with `per_page`. The default is 25 and the maximum is 100; larger values are clamped to 100.

A few rules keep paging predictable:

* Always follow `links.next` verbatim. A hand-edited or truncated cursor returns `422`.
* A cursor is tied to the endpoint and filters it was issued for. To change filters, start again from the first page; reusing a cursor under different filters returns `422`.
* Do not store cursors between sync runs. They are bookmarks for one walk of the list, not durable references. To resume syncing later, store the latest `updated_at` you processed and start a fresh walk with `filter[updated_since]`.

## Filters

Pass filters in the `filter[...]` namespace. Each endpoint documents the filters it accepts.

### Orders

| Filter                      | Accepts                                    |
| --------------------------- | ------------------------------------------ |
| `filter[status]`            | A public order status                      |
| `filter[payment_status]`    | A public payment status                    |
| `filter[fulfilment_method]` | `delivery`, `collection`, or `relay`       |
| `filter[fulfilment_date]`   | A date (`YYYY-MM-DD`) or month (`YYYY-MM`) |
| `filter[updated_since]`     | An ISO 8601 timestamp                      |

### Customers

| Filter                  | Accepts               |
| ----------------------- | --------------------- |
| `filter[updated_since]` | An ISO 8601 timestamp |

### Products

| Filter                  | Accepts               |
| ----------------------- | --------------------- |
| `filter[product_code]`  | A product code        |
| `filter[updated_since]` | An ISO 8601 timestamp |

## Keeping in sync

Use `filter[updated_since]` to fetch only what changed since your last poll. Pass the most recent `updated_at` you have seen, then page through the results. Combine it with webhooks so you can react immediately and reconcile periodically.

An invalid filter value returns `422` with details of what was wrong.
