> ## Documentation Index
> Fetch the complete documentation index at: https://bavlio.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Bavlio's pagination is endpoint-specific — three patterns coexist. The OpenAPI spec is the source of truth for any endpoint's request and response shape.

Bavlio does not yet enforce a unified pagination contract. Three patterns coexist. Check [`/openapi.json`](https://api.bavlio.com/openapi.json) for any endpoint's definitive request and response shape.

## Three patterns in use

### offset + limit

Most list endpoints accept `?offset=0&limit=100`. Default `limit` varies per endpoint (commonly 50 or 100). Continue until response contains fewer rows than `limit`.

```text Example theme={null}
GET /api/v1/campaigns/?offset=0&limit=100
```

### has\_more flag

Some endpoints return `{ items: [...], has_more: bool }`. Continue until `has_more` is false. Use the last item id as the next page anchor where applicable.

```text Example theme={null}
GET /api/v1/personalize/history?after_id=<id>
```

### no pagination

Some endpoints return the full collection in one response (sender profiles, templates). Safe to call once.

```text Example theme={null}
GET /api/v1/sender-profiles/
```

## Drain-all helper

Handles both offset+limit and has\_more shapes. Falls back cleanly when the response is a flat list (no pagination).

```python Python theme={null}
def drain_all(client, path, *, page_size=100, **params):
    """Drain a list endpoint that uses offset+limit pagination."""
    offset = 0
    while True:
        response = client.get(path, params={**params, "offset": offset, "limit": page_size})
        response.raise_for_status()
        items = response.json()
        if not isinstance(items, list):
            payload = items
            items = payload.get("items", [])
            yield from items
            if not payload.get("has_more"):
                return
        else:
            yield from items
            if len(items) < page_size:
                return
        offset += page_size
```

## Common gotchas

* **Default limits vary.** Some endpoints default to 50, some to 100, some to no pagination at all. Always pass an explicit `limit` to make behavior predictable.
* **Order is not stable across pages** for some endpoints under heavy concurrent writes. If you need a consistent snapshot, paginate by a stable id range rather than offset, or freeze the list and resume on failure.
* **Some endpoints have no pagination at all.** Sender profiles and templates return the full collection. Calling them once is correct.

## Roadmap

A unified cursor-based pagination contract (`?cursor=&limit=` with `next_cursor` + `has_more` envelope) is on the roadmap. Until then, treat each endpoint as having its own contract and check the OpenAPI spec.
