# Pagination

> Every collection answers { data, nextCursor, hasMore } with opaque cursors, a published page-size ceiling, strict query parameters and a stable order per endpoint.

Source: https://developers.processcart.com/guides/pagination

Every list in the API answers the same envelope:

```json
{
  "data": [ … ],
  "nextCursor": "eyJ2IjoiMjAyNi0xMC0wN1QxODo0MjoxMS4wMDBaIiwiaWQiOiIuLi4iLCJoIjoiOWI3ZCJ9",
  "hasMore": true
}
```

- `data` is the page.
- `nextCursor` is the opaque token for the next page, or `null` on the last one.
- `hasMore` says whether another page exists.

Bounded lists that always fit in one answer (a run's tasks, a run's files) use the same envelope with `nextCursor: null` and `hasMore: false`, so one loop handles everything.

## Walking a collection

```bash
curl "https://app.processcart.com/api/v1/runs?limit=100" -H "Authorization: Bearer pc_live_…"
# … then, while hasMore:
curl "https://app.processcart.com/api/v1/runs?limit=100&cursor=<nextCursor>" -H "Authorization: Bearer pc_live_…"
```

Send the **same filters and sort** on every page. A cursor is bound to the query that produced it: a cursor sent with a different filter or sort is refused with 422 `validation_failed` (`field: "cursor"`, `code: "cursor_mismatch"`), never silently applied to the wrong list. A cursor the API did not hand out, or one that was altered, is refused the same way with `code: "invalid_cursor"`.

## Page size

`limit` defaults to **50**. The ceiling is **200** on ordinary lists and **100** on `GET /v1/runs` and `GET /v1/views/{id}/runs` (every item there is a full Run with its fields and tasks). A `limit` above the ceiling is a 422 — it is never clamped, so a client cannot silently get less than it asked for.

## Stable order

Each endpoint documents its order. The cursor is a keyset (the sort value and the row id), so a page never repeats or skips a row that did not change. Two orders traverse a changing collection stably: `startedAt` (the default on runs) and `updatedAt asc` (what `updatedSince` forces). Other sorts — `name`, `dueAt`, `keyDate`, `completedAt`, `priority` — may skip or repeat a row whose sort value changes while you are paging. For a full copy, use `startedAt`; for a sync, use [Incremental sync](https://developers.processcart.com/guides/incremental-sync).

## Strict query parameters

Every query parameter is validated and **unknown parameters are refused** with 422 `validation_failed` so a typo cannot silently widen a filter:

```json
{
  "code": "validation_failed",
  "errors": [
    { "field": "updated_since", "code": "unknown_parameter",
      "message": "Unknown query parameter \"updated_since\". Did you mean \"updatedSince\"? Query parameters are camelCase." }
  ]
}
```

Parameters are camelCase everywhere (`updatedSince`, `workflowId`, `includeTrashed`); the message suggests the camelCase twin of the common snake_case spellings (`updated_since`, `workflow_id`, `page_size`, `per_page`, `include_trashed`, …). Comma-separated parameters (`status=active,on_hold`) may also be repeated (`status=active&status=on_hold`).
