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

# Pagination

> Traverse Canary-native, meter-reading, and compatibility lists safely

# Pagination

Canary returns opaque cursors that belong to one endpoint and one set of filters. Send the cursor back unchanged and keep every other query parameter stable throughout a traversal.

## Choose the endpoint variant

| Variant              | Endpoints                                                         | Default | Maximum | Direction         |
| -------------------- | ----------------------------------------------------------------- | ------: | ------: | ----------------- |
| ID cursor            | Assets, locations, parts, meters, native work orders, identifiers |      20 |     100 | `asc` or `desc`   |
| Reading cursor       | Meter readings                                                    |      20 |     100 | `asc` or `desc`   |
| Compatibility cursor | Work requests and request portals                                 |     100 |     200 | Fixed by endpoint |

Custom-field definition lists return a complete array and have no pagination parameters.

## ID cursor

Canary-native resources use the last resource ID as the continuation point. Their response carries pagination under `meta.pagination`:

```json theme={"theme":"github-light"}
{
  "data": [{ "id": "01JAY7C1NQZ5H2R8M4K6T9V3BP" }],
  "meta": {
    "request_id": "req_01JAY8FVDM6CH4R3X9Q2T7K5NP",
    "api_version": "v1",
    "pagination": {
      "cursor": "01JAY7C1NQZ5H2R8M4K6T9V3BP",
      "has_more": true
    }
  }
}
```

```bash theme={"theme":"github-light"}
# First page
curl --fail-with-body \
  "https://api.oncanary.com/v1/assets?status=online&direction=desc&limit=100" \
  --header "Authorization: Bearer $CANARY_API_KEY"

# Following page
curl --fail-with-body \
  "https://api.oncanary.com/v1/assets?status=online&direction=desc&limit=100&cursor=01JAY7C1NQZ5H2R8M4K6T9V3BP" \
  --header "Authorization: Bearer $CANARY_API_KEY"
```

Continue while `has_more` is `true`. Use the returned `cursor` for the next request.

## Reading cursor

Meter readings can share the same timestamp, so their opaque cursor combines `reading_at` and the reading ID. Read the value from `meta.pagination.cursor` and return it unchanged.

```bash theme={"theme":"github-light"}
curl --fail-with-body \
  "https://api.oncanary.com/v1/meters/METER_ID/readings?direction=asc&limit=100&cursor=RETURNED_CURSOR" \
  --header "Authorization: Bearer $CANARY_API_KEY"
```

Starting with `direction=asc` is useful for chronological ingestion. A cursor created for one direction cannot be reused with the other direction.

## Compatibility cursor

Work requests and request portals expose an encoded offset cursor through compatibility response fields:

```json theme={"theme":"github-light"}
{
  "workRequests": [],
  "nextCursor": "d29yay1yZXF1ZXN0OjEwMA",
  "nextPageUrl": "https://api.oncanary.com/v1/workrequests?limit=100&cursor=d29yay1yZXF1ZXN0OjEwMA"
}
```

Use `nextPageUrl` directly or pass `nextCursor` with the same filters. A final page sets both fields to `null`.

Offset cursors reflect the current collection. Concurrent inserts, deletions, or status changes can shift later pages, so recurring synchronizations should reconcile records by ID.

## Traverse a native list

```javascript theme={"theme":"github-light"}
const apiKey = process.env.CANARY_API_KEY;

async function listAllAssets() {
  const assets = [];
  let cursor;

  do {
    const url = new URL('https://api.oncanary.com/v1/assets');
    url.searchParams.set('limit', '100');
    url.searchParams.set('direction', 'asc');
    if (cursor) url.searchParams.set('cursor', cursor);

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    const body = await response.json();
    if (!response.ok) throw new Error(`${body.code}: ${body.detail}`);

    assets.push(...body.data);
    cursor = body.meta.pagination.has_more
      ? body.meta.pagination.cursor
      : undefined;
  } while (cursor);

  return assets;
}
```

## Cursor rules

* Treat every cursor as an opaque, short-lived token.
* Keep filters, page size, direction, endpoint, and organization fixed.
* Restart without a cursor after changing any of those values.
* Deduplicate by resource ID in long-running or compatibility traversals.
* Persist your own synchronization checkpoint after processing a complete page.
