Pagination
Every list endpoint paginates the same way.
GET /api/public/v1/reservations?limit=50
{
"success": true,
"value": {
"items": [],
"nextCursor": "eyJrIjoiMjAyNi0wOS0xNCIsImkiOjg4MjEzfQ"
},
"error": null
}
Pass nextCursor back as cursor for the following page. A null cursor means you have reached the end.
GET /api/public/v1/reservations?limit=50&cursor=eyJrIjoiMjAyNi0wOS0xNCIsImkiOjg4MjEzfQ
Cursors, not offsets
Haven uses keyset cursors rather than page numbers. An offset-paginated list shifts under you when rows are inserted, so a caller walking it will miss records and see others twice. A keyset cursor names a position rather than a count, so insertions do not disturb a walk in progress.
The cursor is opaque. Do not decode, construct or store one beyond the walk it belongs to.
Sizes
limit defaults to 25 and is capped at 100. A larger value is clamped rather than refused, so limit=500 returns 100 without an error. That is friendly to a caller who guessed, and it means you should read what you got rather than assume you got what you asked for.
The one that will bite you
items.length < limit does not mean you have reached the last page.
Haven filters rows your grant cannot see, and it filters them after the page is drawn. A page of 50 can legitimately return 3 items with a non-null cursor. A client that stops when a short page arrives will silently miss data, and this is the single most common bug in a first integration.
Stop when nextCursor is null. Only then.
cursor = None
while True:
page = get("/reservations", limit=50, cursor=cursor)
process(page["value"]["items"]) # may be empty
cursor = page["value"]["nextCursor"]
if cursor is None:
break
No total count
List responses carry no total. Counting the matching rows for every page is expensive on tables this size, and the number would be stale by the time you rendered it. If your interface needs a count, walk the list once and cache what you found.
Incremental reads
Where a resource supports it, updated_since is how you avoid re-walking a portfolio:
GET /api/public/v1/reservations?updated_since=2026-08-30T00:00:00Z
Hold the high-water mark from your last successful complete walk, not from the last page, so an interrupted run resumes correctly rather than skipping what it did not finish.
Prefer webhooks over polling once they ship. Where you must poll, updated_since plus a sensible interval is far cheaper for both sides than a full walk.