Conventions

What every endpoint in this reference has in common, so the per-endpoint pages can stay short.

Requests and responses

JSON in, JSON over HTTPS out. Send Content-Type: application/json on anything with a body. Timestamps are ISO 8601 in UTC (2026-01-15T09:30:00Z), and IDs are UUIDs.

A single record is the whole response body, with no envelope around it:

{ "id": "550e8400-e29b-41d4-a716-446655440000", "email": "[email protected]" }

A list comes back under data, alongside the paging fields described below.

Pagination

List endpoints are cursor paginated. Ask for a page size with limit (1 to 250, 50 by default), and read nextCursor off the response:

{
  "data": [{ "id": "..." }, { "id": "..." }],
  "nextCursor": "WyIyMDI2LTAxLTE1VDA5OjMwOjAwWiIsIjU1MGU4NDAwIl0="
}

Pass that value back as cursor for the next page. When nextCursor is null you have reached the end.

curl "https://api.cascade.dev/customers?limit=250" -H "Authorization: Bearer sk_..."
curl "https://api.cascade.dev/customers?limit=250&cursor=WyIyMDI2..." -H "Authorization: Bearer sk_..."

Cursors encode the sort position of the last record returned, so a record added while you are paging does not shift the pages underneath you and nothing is skipped or repeated. Keep sort and filter the same across a run: change either and the cursor no longer describes where you were.

There is no total count. Walking to the end is the way to count records, and at 250 a page that is one request per 250 records.

Sorting

Pass a field name to sort, prefixed with - to reverse it. Each endpoint lists the fields it accepts. Most default to -createdAt, newest first.

GET /reviews?sort=-rating

Searching and filtering

search is a free-text term matched case-insensitively against a record's most identifying fields. It is the "type a few characters and see" parameter.

filter is a JSON document describing exactly which records you want, URL-encoded on a GET. Each endpoint lists its filterable fields.

GET /customers?filter={"createdAt":{"$gte":"2026-01-01T00:00:00Z"}}

Both can be sent together, and are combined with AND. See Filtering for the full operator set, association filters and logical operators.

Extra fields

Some list endpoints can compute values that are not stored on the record, such as a customer's order count or loyalty balance. Ask for them by name with include:

GET /customers?include=ordersCount,loyaltyPointsBalance

Each one costs an additional query per request, so ask only for what you are about to use. Available values are listed on each endpoint.

Bulk and import endpoints

Three shapes recur, and they behave differently:

  • POST /<resource>/import creates or updates many records at once, matching on a natural key such as an email address, a product slug or your own remoteId. Every column you send overwrites what is stored, so send complete records rather than partial ones. This is the endpoint to use both for a first backfill and for a single record changing in your store. A row only has to carry the fields that identify the record, plus anything the record cannot exist without (a product names at least one site). Leave a field out and nothing is stored under it; leave a collection such as variants or items out and whatever is already stored stays, while sending null replaces it with nothing.

    An import answers with two lists: data, the records as they now stand, and skipped, the rows that named something that could not be found, with the reason. Reviews are the one exception in name only, answering with imported and skipped, because the review bodies are not what the response is about.

  • PATCH /<resource>/bulk-update applies one change to every record matching a filter. Sending no filter applies it to everything in the organization, which is occasionally what you want and never what you want by accident.

  • PATCH /<resource>/{id} updates one record, and only the fields you send.

Import and bulk endpoints do more work than ordinary ones and spend more of your rate limit budget. Where an endpoint costs more, this reference says so beside its permission.

Idempotency

Endpoints that record an event rather than a record take an idempotency key of their own, so a retry after a timeout does not double-count. POST /loyalty-events and POST /loyalty-stamps both work this way. See Loyalty events API.

Everything else is idempotent by construction or not at all: an import matches on a natural key and can be replayed safely, while a POST that creates a record will create a second one if you send it twice.

Rate limits

The admin API is limited per organization, not per key: a second key does not buy a second allowance. Requests you make from the dashboard while signed in do not count. Exceeding the limit returns 429 with a Retry-After header. See Rate limits.

Changes to this API

New fields on a response and new optional parameters can appear at any time, so parse responses in a way that tolerates fields you do not recognize. Removing a field, removing an endpoint or changing what an existing parameter means is a breaking change, and is not something Cascade does silently.

There is no API version in the URL today, and no dated version to pin to.