Skip to main content
View as Markdown
Version: v2

Pagination, filtering, sorting

Core API v2 paginates by cursor only. page and pageSize are not part of the v2 contract, and v2 also changes filtering semantics and the set of list endpoints.

Coming from /v1/... or the OMS API? Upgrading from v1 to v2 lists every difference, and the v1 guides are one switch away in the version picker in the top bar.

Pagination​

v2 has one pagination strategy: cursor pagination. It uses opaque cursors to page through results and stays fast and consistent on large, frequently changing datasets, where offset pagination can skip or repeat rows as data shifts underneath a page number.

Because there is only one strategy, limit is no longer an opt-in switch — it is simply the page size. Send no pagination parameters at all and you get the first page with the default size:

GET /v2/payments
GET /v2/payments?limit=25

limit defaults to 10 and is capped at 100. Send limit=0 to return no items at all, which is how you ask for a count on its own — see Counting the matches.

Every response carries pagination.mode: "cursor".

page and pageSize are rejected, not ignored

A v2 request carrying page or pageSize is answered with 400 and an errors entry naming the parameter. They are not silently dropped: ?page=5 would otherwise return the first page's rows while looking like the fifth, which is a wrong answer you have no way to detect.

What you give up, and what to send instead​

Cursor pagination has no jump-to-page. v2 responses carry no pageCount, and there is no way to ask for "page 5".

  • To walk a whole result set, follow nextCursor until hasNextPage is false.
  • To show a result count, send withTotal=true — see Counting the matches.
  • To show which rows you are on ("21–40"), keep a running total of the rows you have already received. Add data.length to it each time you step forward and subtract the page you leave behind when you step back. Do not multiply the page number by limit — limit may change between pages, so the two disagree as soon as it does.

Counting the matches​

Send withTotal=true and the response's pagination gains totalCount, the number of items your filters match:

GET /v2/payments?limit=20&status[eq]=cancelled&withTotal=true
{
"pagination": {
"mode": "cursor",
"pageSize": 20,
"hasNextPage": true,
"hasPreviousPage": false,
"nextCursor": "eyJzIjoiY3JlYXRlZEF0IiwiZCI6ImFzYyIsIm4iOiJmb3J3YXJkIn0",
"prevCursor": null,
"totalCount": 139,
"totalCountIsExact": true
},
"data": [
/* Array of items */
]
}

Three things to know before you show that number:

Check totalCountIsExact first. Counting stops at 10,000. When more items match, totalCount holds 10,000 and totalCountIsExact is false — the real figure is higher and unknown, so show it as a bound like "over 10,000" rather than as a total.

The count ignores cursor. It describes everything your filters match, not what is left ahead of you, so it does not change as you page. Request it once when the filters change rather than on every page.

Omit it when you do not need it. A request without withTotal does no counting at all.

To fetch the count on its own — when the filters change but you already have the rows, or when you want the count and the page to load independently — pair it with limit=0:

GET /v2/payments?limit=0&status[eq]=cancelled&withTotal=true

That returns an empty data array and no cursors, because a response with no rows is not a page you can navigate from.

Narrow the list before counting

If a count feels slow on a large dataset, add a date range alongside your other filters.

Paging​

On the first request, send limit and omit cursor:

GET /v2/payments?limit=10

To fetch another page, pass the returned nextCursor or prevCursor value back in cursor, keeping the same sortBy and sortDirection:

GET /v2/payments?limit=10&sortBy=createdAt&sortDirection=desc&cursor=eyJzIjoiY3JlYXRlZEF0IiwiZCI6ImRlc2MiLCJuIjoiZm9yd2FyZCJ9

A cursor encodes the sort field and direction it was issued for, and a request whose sortBy or sortDirection disagrees is rejected. limit is not part of the cursor — you may change it between pages, and it simply changes the size of the next page. Cursors are opaque; do not construct or modify them.

An invalid limit falls back to the default page size. 0 is valid and means "no items"; a negative number is not.

Sorting​

Sort with sortBy and sortDirection:

GET /v2/payments?sortBy=createdAt&sortDirection=desc

Each endpoint supports its own sortBy fields. The v2 field set is listed in Supported v2 endpoints.

Filtering​

Filter criteria use LHS bracket notation:

GET /v2/payments?amount[gt]=100&status[eq]=succeeded

The v2 filter operator set is:

Data typeOperators
Booleaneq, ne
Numbereq, ne, gt, gte, lt, lte, in, nin
Stringeq, ne, like, ilike, in, nin
String (exact match)eq, ne, in, nin
Dateeq, ne, gt, gte, lt, lte, in, nin
JSON metadata valueseq, ne, like, in, nin

contains is not in this table because it depends on the field rather than on the data type: it applies only to a field that holds several values, such as supportedPaymentMethods. This is the same in v1 and v2. The API reference lists it on those fields, and sending it on another field the endpoint publishes is answered with 400, naming the field.

A field marked String (exact match) in Supported v2 endpoints is matched against its whole value: an ID, a reference to another object, a field that holds one of a fixed set of values such as status, currency or type, or card digits such as card.lastFour. These accept eq, ne, in and nin. like or ilike on one of them is answered with 400, naming the field. To match several values, list them with in.

The processor ID filters, processorId on Transactions and latestTransaction.processorId on Payments, accept only eq and in. Send the whole processor ID, in the case the processor returned it. url on Logs also accepts only eq and in: send the whole URL as the log entry shows it, query string included. Any other operator on these fields is answered with 400, naming the field.

Filtering by a range of card BINs​

paymentMethod.card.bin on Payments and on the payment summary report (GET /v2/reports/payment-summary) also accepts gte and lte, so you can select every payment whose card BIN is inside a range:

GET /v2/payments?paymentMethod.card.bin[gte]=468800&paymentMethod.card.bin[lte]=468899

A BIN is the first 4 to 8 digits of a card number, so the range compares the card numbers each BIN starts, not the text. A bound stands for every card number that starts with it: gte=4688 and lte=4688 together select every BIN that begins with 4688, whether it is 4, 6 or 8 digits long. You can send either bound on its own.

A payment matches only when every card number its BIN starts is inside the range. When your bounds have more digits than a payment's BIN, that payment can fall on both sides of a bound, so it is left out. For example, a payment with BIN 468839 is not returned for gte=46883950 and lte=46883999. Each payment returns its BIN in paymentMethod.card.bin, so you can see how many digits it has. To include such payments, use bounds with no more digits than the BIN.

eq, ne, in and nin still match the whole BIN exactly. A bound that is not 1 to 8 digits, a gte above the lte, and gt or lt on this field are answered with 400, naming the field.

Case sensitivity​

String filters are case-sensitive in v2 unless you explicitly use ilike. For example, ?status[eq]=Succeeded can match succeeded in v1, but in v2 it returns an empty page if the stored value is lowercase. Send an exact-match field such as status in the case the API returns it. On a text field, use ilike when you want case-insensitive matching, for example ?name[ilike]=acme.

Email addresses are the exception. ODUS stores customer.email and the Blacklist email in lowercase, and lowercases the value you filter by, so ?email[eq]=Foo@Example.com and ?email[eq]=foo@example.com return the same customer. Use eq for an email lookup.

The like operator is also different in v2: it is a prefix match (value%), not the v1 substring match (%value%).

Silent filter changes

A case mismatch or a v1-style substring like query does not return an error in v2. It simply returns fewer rows, often an empty page.

Using in and nin​

Pass array values by repeating the query parameter:

GET /v2/payments?status[in]=succeeded&status[in]=pending

Comma notation (for example, status[in]=succeeded,pending) is parsed literally and is not supported.

Using $and and $or​

The structural $and and $or operators remain available in v2. They are query-string structures, so they are not listed as scalar OpenAPI parameters.

$or is supported only at the root level, and one $or group cannot be nested inside another. It can be combined with other root filter criteria: those are applied alongside the group with logical AND. Conditions inside one $or group are also combined with logical AND.

GET /v2/payments?$or[0][status][eq]=succeeded&$or[0][isCaptured][eq]=true&$or[1][status][eq]=succeeded&$or[1][isReversed][eq]=true&$or[1][isCaptured][eq]=true

Use $and when multiple contains conditions must all be true:

GET /v2/gateway-profiles?$and[0][supportedPaymentMethods][contains]=applepay&$and[1][supportedPaymentMethods][contains]=card

JSON metadata filtering​

JSON fields, such as metadata, support filtering by key with bracket notation. Metadata value filters are case-sensitive.

GET /v2/payments?metadata[field1][eq]=value

When filtering metadata fields with ne, nin, like, in, or comparison operators, entities without that metadata key are excluded due to SQL NULL semantics. To include missing keys, use $or:

GET /v2/payments?$or[0][metadata][field1][ne]=test&$or[1][metadata][field1][eq]=null

Supported v2 endpoints​

The Users, Jobs, and Job Items list endpoints that appear in the v1 guide are not served by v2.

Accounts​

Endpoint: GET /v2/accounts

Sortable fields: id, name, createdAt

Filterable fields:

FieldData Type
idString (exact match)
nameString
createdAtDate

Cascades​

Endpoint: GET /v2/cascades

Sortable fields: id, createdAt, updatedAt, active, name

Filterable fields:

FieldData Type
idString (exact match)
nameString
activeBoolean
algorithmString (exact match)
createdAtDate
updatedAtDate

Customers​

Endpoint: GET /v2/customers

Sortable fields: id, createdAt, updatedAt

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
emailString
nameString
phoneNumberString
dateOfBirthDate
restrictCitBoolean
restrictMitBoolean
restrictReasonString
metadataJSON

Gateway Operations​

Endpoint: GET /v2/gateway-operations

Sortable fields: id, createdAt, updatedAt, type, status

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
typeString (exact match)
statusString (exact match)
transactionString (exact match)

Gateway Profiles​

Endpoint: GET /v2/gateway-profiles

Sortable fields: id, createdAt, updatedAt

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
titleString
integrationString (exact match)
activeBoolean
supportedPaymentMethodsString

Invoices​

Endpoint: GET /v2/invoices

Sortable fields: id, createdAt, updatedAt, status

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
statusString (exact match)
currencyString (exact match)
amountNumber
voidedReasonString
periodStartDate
periodEndDate
paymentString (exact match)
numberString
subscriptionString (exact match)

Logs​

Endpoint: GET /v2/logs

Sortable fields: id, createdAt, updatedAt

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
requestIdString (exact match)
urlString (eq and in only)
methodString (exact match)
clientIpString (exact match)
statusCodeNumber
initiatorString (exact match)

Payment Methods​

Endpoint: GET /v2/payment-methods

Sortable fields: id, createdAt, updatedAt, isDefault, customer

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
isDefaultBoolean
customerString (exact match)
card.lastFourString (exact match)
card.brandString (exact match)
card.expYearString (exact match)
card.expMonthString (exact match)

Payments​

Endpoint: GET /v2/payments

Sortable fields: id, createdAt, updatedAt, amount, status, currency

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
amountNumber
statusString (exact match)
displayStatusString (exact match)
currencyString (exact match)
amountCapturedNumber
amountReversedNumber
captureMethodString (exact match)
captureDelayNumber
captureAtDate
statementDescriptorString
isCapturedBoolean
isReversedBoolean
isFullyReversedBoolean
isChargebackedBoolean
isRedirectRequiredBoolean
initiatorString (exact match)
offSessionTypeString (exact match)
latestTransactionString (exact match)
latestTransaction.processorIdString (eq and in only)
latestTransaction.gatewayProfile.idString (exact match)
latestTransaction.gatewayProfile.titleString
customerString (exact match)
customer.emailString
paymentMethodString (exact match)
paymentMethod.typeString (exact match)
paymentMethod.card.lastFourString (exact match)
paymentMethod.card.binString (exact match), plus a BIN range
paymentMethod.card.brandString (exact match)
paymentMethod.card.expYearString (exact match)
paymentMethod.card.expMonthString (exact match)
paymentMethodOptions.card.gatewayProfileString (exact match)
paymentMethodOptions.card.cascadeString (exact match)
paymentMethodOptions.card.modeString (exact match)
paymentMethodOptions.paypal.gatewayProfileString (exact match)
product.nameString
metadataJSON
productString (exact match)
latestDeclineTypeString (exact match)

Prices​

Endpoint: GET /v2/prices

Sortable fields: id, createdAt, updatedAt, amount, type

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
amountNumber
currencyString (exact match)
typeString (exact match)
nameString
productString (exact match)
product.nameString
activeBoolean
metadataJSON

Products​

Endpoint: GET /v2/products

Sortable fields: id, createdAt, updatedAt, name, active

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
nameString
activeBoolean
shippableBoolean
categoryString (exact match)
skuString
metadataJSON

Secret Keys​

Endpoint: GET /v2/secret-keys

Sortable fields: id, createdAt, updatedAt

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
activeBoolean

Subscriptions​

Endpoint: GET /v2/subscriptions

Sortable fields: id, createdAt, updatedAt, status, currentPeriodStart, currentPeriodEnd, startDate, endDate

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
statusString (exact match)
startDateDate
endDateDate
autoBillingEnabledBoolean
autoBillingDisabledAtDate
autoBillingDisabledReasonString (exact match)
cancelAtPeriodEndBoolean
cancelAtCycleNumber
currentPeriodStartDate
currentPeriodEndDate
currentCycleNumber
metadataJSON
customerString (exact match)
customer.emailString
priceString (exact match)
price.productString (exact match)
setupInvoice.paymentString (exact match)
price.product.nameString
upcomingInvoice.periodStartDate
invoices.payment.retry.nextAtDate

Transactions​

Endpoint: GET /v2/transactions

Sortable fields: id, createdAt, updatedAt, amount, status

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate
amountNumber
currencyString (exact match)
statusString (exact match)
processorIdString (eq and in only)
paymentMethodString (exact match)
paymentString (exact match)
gatewayProfileString (exact match)
customerString (exact match)
executionString (exact match)
paymentMethod.card.binString (exact match)
paymentMethod.card.brandString (exact match)

Webhook Subscribers​

Endpoint: GET /v2/webhook-subscribers

Sortable fields: id, createdAt, updatedAt

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
updatedAtDate

Webhook deliveries​

Endpoint: GET /v2/webhook-deliveries

Sortable fields: id, createdAt, firstDispatchedAt, lastDispatchedAt, abandonedAt, attempts

Filterable fields:

FieldData Type
idString (exact match)
createdAtDate
firstDispatchedAtDate
lastDispatchedAtDate
abandonedAtDate
successBoolean
attemptsNumber
latestResponseStatusNumber
event.typeString (exact match)
eventString (exact match)
subscriberString (exact match)