Developers

Pagination, field masks & rate limits

Cursor pagination, ?fields= masking, and the rate-limit headers on every response.

Read as markdown · OpenAPI 3.1 · llms.txt

Cursors, not pages

Every list endpoint returns:

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

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.