# Pagination

Every list endpoint paginates the same way.

```
GET /api/public/v1/reservations?limit=50
```

```json
{
  "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.

```python
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.
