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=<the last one you got> |
| 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
{
"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
{
"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.