Skip to main content

Pagination

Every list endpoint on 2026-05-01 uses opaque cursor pagination. No offsets, no total count.

Response shape

  • data — the page of items.
  • next_cursor — opaque signed string. Pass back as ?cursor=<value> to get the next page. null means you’ve reached the end.

Iteration pattern

TypeScript:
Python:

Limits

Always request as large a page as you can tolerate — fewer roundtrips, less rate pressure.

Sort order

List endpoints sort by (created_at DESC, id DESC) on immutable columns. This guarantees pagination correctness when rows are added or modified mid-scan — no skips, no duplicates — at the cost of “sort by updated_at” being unavailable. Client-side sort if you need a different order.

No total

Canonical responses do not include a total count. Computing it on every list call would pressure the DB. If you need an approximate count for a dashboard, cache it separately (e.g. run a count query daily). For a progress bar on a long iteration, the common pattern is “processed 127 so far…” without a denominator.

Opaque cursors

Cursors are HMAC-signed + base64url-encoded. Don’t:
  • Parse or mutate the cursor value.
  • Attempt to construct one by hand.
  • Reuse a cursor from a different list endpoint.
The server detects tampering and returns 400 invalid_request. Cursors are valid across sessions but have a bounded TTL — don’t store one for a week and expect it to resume correctly. For long-running iterations (> a few hours), consider re-starting from the beginning.

Endpoints that paginate

All of these return {data, next_cursor}:
  • GET /v1/canvases
  • GET /v1/canvases/search
  • GET /v1/tasks
  • GET /v1/brand-kits
  • GET /v1/organizations
  • GET /v1/events
Single-resource endpoints (GET /v1/canvases/{id}, GET /v1/tasks/{id}, GET /v1/credits, etc.) don’t paginate.

Concurrency

Don’t parallelize pagination. Each page depends on the previous one’s cursor. Parallelize the work done per item inside a page instead.

Migration from offset pagination

If you’re carrying code from the legacy 2026-04-12 shape:

Common wrong guesses

  • Expecting total in the response. Not there. Compute separately if you need it.
  • Parsing / mutating cursors. Opaque; server rejects tampered values.
  • Passing offset= as a fallback. Ignored (or rejected, depending on endpoint) on 2026-05-01. Use cursor.
  • Holding a cursor for days. Cursors expire; restart the iteration.
  • Parallel page fetches. Each page depends on the prior cursor.
  • Reading response.canvases / response.tasks / response.brand_kits. Canonical always puts items under data.

Upstream