# Pagination, field masks & rate limits

## Cursors, not pages

Every list endpoint returns:

```json
{
  "data": [ … ],
  "next_cursor": "djE6Y2x4…",
  "has_more": true
}
```

Pass `next_cursor` back as `?cursor=` to get the next page. When `has_more` is false, `next_cursor`
is null and you are done.

```bash
cursor=""
while :; do
  page=$(curl -sG https://app.zellerai.co/api/v1/contacts \
    -H "Authorization: token $KEY" \
    --data-urlencode "limit=100" \
    ${cursor:+--data-urlencode "cursor=$cursor"})
  echo "$page" | jq -c '.data[]'
  cursor=$(echo "$page" | jq -r '.next_cursor // empty')
  [ -z "$cursor" ] && break
done
```

**Why not `?page=2`?** Because rows are being created while you walk. An offset pager shifts under
you: insert a row between page 1 and page 2 and you will see one row twice and miss another
entirely. A cursor is a position in a total order, so it cannot skip or repeat. Treat it as opaque —
it encodes a version and an id today, and that is not a promise.

A malformed or stale cursor restarts the walk rather than erroring.

`limit` defaults to 50 and is capped at 200.

## Field masks

Ask for less:

```
GET /api/v1/contacts?fields=id,first_name,stage
```

`id` is always included — a response you cannot re-fetch is not smaller, it is broken. This is
bandwidth, not security: the scope check already happened, and asking for fewer fields never grants
more.

## Rate limits

Every response carries:

```
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1786000260
```

A 429 adds `Retry-After` in seconds. Limits are **120/min** for an API key and **300/min** for an
OAuth application, counted per app **per workspace** — so one noisy customer of your integration
cannot exhaust the budget for the others.

The limiter is durable (Postgres-backed), not per-process, so the limit is the limit no matter how
many instances answer.
