Pagination

Cursor-based, opaque, stable across writes. Filter and sort grammar.

List shape

GET /v2/<surface>/<resource>
  ?sort=<field>|-<field>[,...]
  &filter[<field>]=<value>
  &filter[<field>][op]=<value>         # for non-equality
  &page[size]=<n>
  &page[after]=<cursor>
  &page[before]=<cursor>
  &include=<rel>[,<rel>...]            # optional embed

→ 200 {
  "data": [<Resource>...],
  "page": {
    "next_cursor": "...",
    "prev_cursor": "...",
    "has_more": true,
    "size": 25
  },
  "meta": { "count": 1234 }       # only present when ?meta=count is requested
}

Sort

  • One or more fields, comma-separated.
  • - prefix for descending; no prefix = ascending.
  • Per-endpoint sortable-field whitelist declared in OpenAPI. Sorts on non-whitelisted fields return 400 invalid_sort_field.
  • Default sort per endpoint (typically -inserted_at).
?sort=-launched_at
?sort=-launched_at,name

Filter

  • filter[<field>]=<value> for equality.
  • filter[<field>][<op>]=<value> for comparisons.
  • Allowed ops: eq, neq, lt, lte, gt, gte, in (comma-separated), nin, contains, starts_with, ends_with, present, missing.
  • Multiple filters AND together. For OR semantics on contact lists, use a Cohort.
?filter[status]=launched
?filter[launched_at][gte]=2026-01-01
?filter[objective_keys][in]=direct_deposit,card_activation

Pagination

Cursor-based, opaque, stable across writes. Cursors are base64-encoded JSON of {sort_key_values, position_token, sort_signature}. Clients treat them as opaque.

  • page[size] is the requested page size. Server caps at endpoint-defined max (typically 100). Default 25.
  • page[after]=<cursor> returns the next page. page[before]=<cursor> returns the previous page.
  • Newly-inserted rows that match the filter appear at the boundaries of the current pagination window, not retroactively in pages the client already fetched.
  • Cursors stay valid until the underlying sort or filter shape changes. If they don't, the server returns 410 cursor_invalid — re-fetch from page 1.

Count

meta.count is returned only when requested via ?meta=count. Counting is expensive; opt-in.

For aggregate-shaped responses (rates, time series), count is part of the typed payload, not the meta.

Include (relationship embed)

?include=<rel>[,<rel>] embeds related resources in the response. Embedded resources land in data[].relationships.<rel> as fully-typed resources (not just IDs).

Per-endpoint includeable-relationship whitelist declared in OpenAPI. Embedding does NOT bypass authorization — the consumer must have read scope on the included resource type.

Errors

CodeHTTPMeaning
invalid_sort_field400Sort references a non-whitelisted field
invalid_filter_field400Filter references a non-whitelisted field
invalid_filter_op400Filter uses an unsupported op
invalid_page_size400Exceeds endpoint max
cursor_invalid410Sort or filter shape changed since cursor was issued