Skip to main content
Every list endpoint — /offers, /rentals, /rentals/{id}/events — is cursor-paginated and returns the same envelope:
Follow meta.next_cursor until it is null.

A short page is not the last page

A page can come back with fewer items than limit while next_cursor is still set. Stop when next_cursor is null, never when a page looks short.
Some filters are applied after the page is read from the database, so a page of 50 rows can arrive as 12 after filtering. The loop that stops on len(page) < limit will silently drop the rest of your results — and it will do it intermittently, which is worse than doing it always.

Cursors are bound to their query

A cursor encodes the position within a specific query. Replay it against different filters or a different sort and you get 400 invalid_cursor rather than a silently restarted or scrambled result set:
In practice: keep the filters in a variable and send the same ones on every page, as both examples above do. Do not rebuild the query string between pages. Cursors are opaque. Today’s happens to be base64; that is not a promise. Do not parse, construct, or store them beyond the life of the loop.

Limits

Above 200 you get 400 invalid_request. Very deep paging is refused with pagination_limit_exceeded — at that depth the answer is a narrower filter, not more pages.

There is no total count

Deliberately. A count over a live marketplace is stale the moment it is computed — offers appear and disappear as suppliers publish and buyers rent — so a total: 412 would be a number we cannot stand behind, printed with the authority of one we can. If you need a count for display, count what you actually received and say so (“showing 87 offers”). If you need it for a progress bar, prefer a spinner: an honest indeterminate beats a precise fiction.

Ordering

Within a single cursor walk, ordering is stable. Across walks it is not: the catalog changes underneath you, and an offer that was on page 1 an hour ago may be gone entirely.