# Lists, paging and filters Every endpoint that returns more than one record follows the same contract, so you write the paging code once. ### Two kinds of paging, and they are not interchangeable | | Page numbers | Cursor | |---|---|---| | You send | `?page=2&per_page=50` | `?cursor=` | | You get back | `links` and `meta` | `next_cursor` and `prev_cursor` | | Used by | Most listings: contacts, tags, deals, quotes, imports… | Feeds that keep growing: a contact's activity, a conversation's messages | | Total known | Yes, in `meta.total` | No, on purpose | Page numbers are the default. **Cursors are used exactly where a page number would lie:** a timeline grows while you read it, so page 2 half an hour later is not the page 2 you would have got. Follow `next_cursor` until it comes back `null`; never build `?page=2` by hand on those endpoints. ### The page-number envelope ```json { "data": [ … ], "links": { "first": "…?page=1", "last": "…?page=4", "prev": null, "next": "…?page=2" }, "meta": { "current_page": 1, "from": 1, "to": 25, "per_page": 25, "last_page": 4, "total": 87 } } ``` Walk it with `links.next` rather than by incrementing `current_page`: when `links.next` is `null` you are done, and that stays true if the shape ever gains a filter you did not send. ### The cursor envelope ```json { "data": [ … ], "next_cursor": "eyJpZCI6ODgxfQ", "prev_cursor": null } ``` ### `per_page` Optional everywhere. Every endpoint caps it, because an integration asking for `per_page=100000` is not malicious — it is just written in a hurry — and the honest answer to that is a ceiling rather than a timeout. | Endpoints | Default | Maximum | |---|---|---| | Most listings | 20–25 | **100** | | Commercial API (`/api/v1/*`) | 50 | **100** | | Audit log · conversation messages | 50 | **200** | Above the ceiling you are silently given the ceiling — `meta.per_page` tells you how many you really got. The audit log is the exception and answers `422`: there, quietly returning fewer rows than were asked for would read as "nothing else happened". ### Filters Filters are query parameters, they are **additive** (every one you send narrows the result further), and each endpoint documents its own. There is no shared filter language: `?status=open` on deals and `?unread=true` on notifications are each defined where they are used. Two rules hold everywhere: - **An unknown filter is ignored, not rejected.** A typo silently widens your result instead of failing, so check that what you sent is in the endpoint's parameter list. - **Filters never cross the location.** They narrow what your token can already see; there is no parameter that widens it. ### Sorting Listings come back in the order that makes sense for what they are — newest first for anything with a timeline, alphabetical for catalogues like tags — and each endpoint says so. Where sorting is configurable, the endpoint documents its own parameter; there is no global `sort=` and adding one is not planned.