> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://api-docs.papertracc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://api-docs.papertracc.com/_mcp/server.

# Pagination

Lists are paged with a cursor. Each response returns a `last_id` and a `keyset`. Send both back to get the next page, and stop when `last_id` comes back `null`.

| Part                                | Cursor (`size`, `last_id`, `keyset`) | Page numbers (`page`, `size`) |
| ----------------------------------- | ------------------------------------ | ----------------------------- |
| [Core](#core-endpoints)             | Yes                                  | No                            |
| [Storefront](#storefront-endpoints) | Yes                                  | Still accepted                |

> **Note**
>
> Storefront lists used to be paged by page number only. They now return a cursor too, and it's the way to page through them. Page numbers still work for numbered page links, but move "load more", infinite scroll and syncing over to `last_id` and `keyset`. A cursor doesn't skip or repeat records when new ones are added while you page, and it stays fast however far you go.

## Request parameters

| Parameter | Description                                                                                                                         |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `size`    | Number of records to return. Defaults to 20, capped at 50.                                                                          |
| `last_id` | The `last_id` from the previous response. Omit it to fetch the first page.                                                          |
| `keyset`  | The `keyset` from the previous response. Always send it with `last_id`: both come from the same page and mean nothing on their own. |

`last_id` and `keyset` are opaque. Don't parse them or build them yourself. Copy them from one response into the next request.

Send the same filters, search and `sort_by` on every page as you sent for the first one. To change any of them, start again without a cursor.

## Paging through a list

Fetch the first page without `last_id` or `keyset`:

```bash
curl "https://api.papertracc.com/v1/invoices?size=20" \
  -H "Authorization: Bearer $API_KEY" \
  -H "identity: $WORKSPACE_IDENTITY"
```

```json
{
  "status": true,
  "message": "Successfully fetched invoices lists",
  "data": [
    { "id": "004c2905-c598-4d12-8600-cf46d8cbaecd", "invoice_reference": "INV-0165", "...": "..." }
  ],
  "keyset": "MjAyNi0wOS0yM1QxMTowNTowNS43NDM1NDda",
  "last_id": "004c2905-c598-4d12-8600-cf46d8cbaecd"
}
```

Then pass the `last_id` and `keyset` you got back as query parameters:

```bash
curl "https://api.papertracc.com/v1/invoices?size=20&last_id=004c2905-c598-4d12-8600-cf46d8cbaecd&keyset=MjAyNi0wOS0yM1QxMTowNTowNS43NDM1NDda" \
  -H "Authorization: Bearer $API_KEY" \
  -H "identity: $WORKSPACE_IDENTITY"
```

Keep going with the pair from each response. You've reached the end when a response comes back with `last_id: null`, which can arrive with an empty `data` list. Stop there rather than counting pages.

## Core endpoints

Core lists such as invoices, receipts, bills, expenses and journal entries take a cursor only. A `page` or `offset` parameter is ignored, so sending one just returns the first page again.

Core lists are always returned newest first (`created_at` descending). They don't take `sort_by`.

## Storefront endpoints

These Storefront lists return a cursor:

* List Products
* List Collections
* Get Collection (its products)
* List Articles
* List Comments
* List Reviews
* List Orders

`sort_by` works with cursors, including price and title sorts. Products with no price sort last in both price orders.

```bash
curl "https://api.papertracc.com/v1/store/products?size=24&sort_by=price-ascending" \
  -H "Authorization: Bearer $API_KEY" \
  -H "identity: $WORKSPACE_IDENTITY" \
  -H "channel: $CHANNEL_HANDLE"
```

```json
{
  "status": true,
  "message": "Successfully fetched products",
  "count": 57,
  "pages": 3,
  "page": 1,
  "keyset": "WyIxMjUwMCJd",
  "last_id": "7464c94f-32e1-4fc9-971a-9828766a6d9f",
  "data": [
    { "id": "7464c94f-32e1-4fc9-971a-9828766a6d9f", "handle": "silk-scarf", "...": "..." }
  ]
}
```

```bash
curl "https://api.papertracc.com/v1/store/products?size=24&sort_by=price-ascending&last_id=7464c94f-32e1-4fc9-971a-9828766a6d9f&keyset=WyIxMjUwMCJd" \
  -H "Authorization: Bearer $API_KEY" \
  -H "identity: $WORKSPACE_IDENTITY" \
  -H "channel: $CHANNEL_HANDLE"
```

| Field               | Description                                                                           |
| ------------------- | ------------------------------------------------------------------------------------- |
| `keyset`, `last_id` | Send both back for the next page. `null` on the last page.                            |
| `count`             | Records that match the request across all pages.                                      |
| `pages`             | Total pages at the requested `size`.                                                  |
| `page`              | The page returned when you paged by number. `0` when you sent `last_id` and `keyset`. |

Get Collection and List Reviews put these fields inside `data`, next to the collection or the rating.

### Page numbers

Storefront lists still accept `page`, starting at 1, so a shop can show numbered pages and jump between them. A `page` past the last one returns an empty list. When a request has `last_id` and `keyset`, `page` is ignored.

Every response, whether you paged by number or by cursor, includes `last_id` and `keyset`. You can switch to the cursor from any numbered page.