Pagination
Cursor-based, both directions, constant cost at any history size
List endpoints (GET /v1/emails, /v1/api-keys, /v1/workspaces) paginate
by cursor and always return rows newest-first.
The envelope
Walking the list
- First request — no cursor, optionally
limit(1–100, default 20). - Older items: request again with
?cursor=<next_cursor>. - Newer items:
?cursor=<prev_cursor>. From the top of the list this returns rows created after your first request — a built-in “load fresh” poll; an emptydatameans you’re at the top. - Stop when
next_cursorisnull.
Rules cursors enforce
- Keep the query identical between hops. The cursor is fingerprinted with
your filters and
limit; changing them mid-walk returns400 validation.cursor_filters_mismatch. Start a fresh walk instead. - Cursors are opaque. Don’t parse or construct them — malformed values
return
400 validation.cursor_invalid.
About total
Counting is expensive on large histories, so total appears only on the
first page (no cursor) and is capped at 10 000: "total": 10000, "total_capped": true means “10 000 or more”. While walking with cursors,
total is null — use has_next/has_prev.