Pagination, filtering, and conditional requests

Envelope

Single resource:

{ "data": { "object": "product", "id": "prd_12", "name": "Sales course" } }

Collection:

{
  "data": [ { "object": "product", "id": "prd_12" } ],
  "page": { "limit": 50, "next_cursor": "eyJ2IjoxLCJ...", "prev_cursor": null }
}

Every resource includes object and id. A related resource appears as a reference ({ "object": "product", "id": "prd_12" }) or as the related public ID. To get the full related resource, retrieve it with its own operation.

Cursor pagination

GET /api/v3/users?limit=100&cursor=eyJ2IjoxLCJ...
  • limit ranges from 1 to 200 and defaults to 50.
  • To read the next page, send the next_cursor value in the cursor parameter. A null next_cursor means you have reached the end of the collection. There is no total page count and no offset.
  • The cursor is opaque and signed, and it is valid for 24 hours. A cursor that is malformed, altered, or expired returns 400 invalid_cursor.
  • Reuse a cursor only with the same filters and sort value that produced it. Changing them while you page through a collection does not produce consistent results.

Not every collection paginates. Some operations return all matching items in a single response, using the same collection envelope with next_cursor set to null. If you always follow next_cursor until it is null, your code handles both cases.

Filtering and sorting

Each operation in the reference declares the parameters it accepts. Pagination, sorting, and filters are not universal: check the operation before you rely on limit, cursor, sort, or a specific filter.

  • Filters are plain query parameters documented per operation, for example status=published, external_id=erp-1001, created_after=2026-09-01T00:00:00Z, or q= for text search.
  • sort= accepts only the values the operation declares. The - prefix reverses the order (sort=-created_at). An unsupported limit or sort value returns 422 validation_failed.
  • Dates and times use RFC 3339. Responses always return UTC with Z and milliseconds. Requests accept any offset.
  • A parameter the operation does not declare returns 400 unknown_parameter. The API never silently ignores an unknown parameter.
  • Collections that support the trash accept deleted=true to list items in the trash. To restore an item, send a PATCH with deleted: false.
  • Collections return only what the current credentials can see. Access rules are applied before pagination, so every page contains only accessible items.

For the full list of error codes, see Errors.

Conditional reads and writes

A GET for a single resource returns an ETag:

ETag: W/"product:prd_12:2026-09-21T15:53:20.123456Z"

Treat the value as opaque. Store it and send it back in If-Match when you write:

PATCH /api/v3/products/prd_12
If-Match: W/"product:prd_12:2026-09-21T15:53:20.123456Z"

If the resource changed after you read it, the response is 412 revision_mismatch and nothing is saved. If-Match is optional: without it, the write applies to the current version of the resource, and you lose this protection. Send it on every PATCH and PUT .../order that accepts it, especially on content resources.

Fields

  • PATCH is partial: an omitted field is left unchanged, and null clears the field when it accepts null.
  • PUT on a set sub-resource (/order, /tags, /content) replaces the entire set.
  • Responses include every field in the schema, with explicit null values. A request body field that is not in the schema returns 422 unknown_field.
  • Monetary amounts are strings, returned with a currency field alongside. Booleans are never 0 or 1. Enum values are stable English strings.

On this page