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 ignoredA 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
nextCursoruntilhasNextPageisfalse. - 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.lengthto it each time you step forward and subtract the page you leave behind when you step back. Do not multiply the page number bylimit—limitmay 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.
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 type | Operators |
|---|---|
| Boolean | eq, ne |
| Number | eq, ne, gt, gte, lt, lte, in, nin |
| String | eq, ne, like, ilike, in, nin |
| String (exact match) | eq, ne, in, nin |
| Date | eq, ne, gt, gte, lt, lte, in, nin |
| JSON metadata values | eq, 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%).
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:
| Field | Data Type |
|---|---|
id | String (exact match) |
name | String |
createdAt | Date |
Cascades
Endpoint: GET /v2/cascades
Sortable fields: id, createdAt, updatedAt, active, name
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
name | String |
active | Boolean |
algorithm | String (exact match) |
createdAt | Date |
updatedAt | Date |
Customers
Endpoint: GET /v2/customers
Sortable fields: id, createdAt, updatedAt
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
email | String |
name | String |
phoneNumber | String |
dateOfBirth | Date |
restrictCit | Boolean |
restrictMit | Boolean |
restrictReason | String |
metadata | JSON |
Gateway Operations
Endpoint: GET /v2/gateway-operations
Sortable fields: id, createdAt, updatedAt, type, status
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
type | String (exact match) |
status | String (exact match) |
transaction | String (exact match) |
Gateway Profiles
Endpoint: GET /v2/gateway-profiles
Sortable fields: id, createdAt, updatedAt
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
title | String |
integration | String (exact match) |
active | Boolean |
supportedPaymentMethods | String |
Invoices
Endpoint: GET /v2/invoices
Sortable fields: id, createdAt, updatedAt, status
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
status | String (exact match) |
currency | String (exact match) |
amount | Number |
voidedReason | String |
periodStart | Date |
periodEnd | Date |
payment | String (exact match) |
number | String |
subscription | String (exact match) |
Logs
Endpoint: GET /v2/logs
Sortable fields: id, createdAt, updatedAt
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
requestId | String (exact match) |
url | String (eq and in only) |
method | String (exact match) |
clientIp | String (exact match) |
statusCode | Number |
initiator | String (exact match) |
Payment Methods
Endpoint: GET /v2/payment-methods
Sortable fields: id, createdAt, updatedAt, isDefault, customer
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
isDefault | Boolean |
customer | String (exact match) |
card.lastFour | String (exact match) |
card.brand | String (exact match) |
card.expYear | String (exact match) |
card.expMonth | String (exact match) |
Payments
Endpoint: GET /v2/payments
Sortable fields: id, createdAt, updatedAt, amount, status, currency
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
amount | Number |
status | String (exact match) |
displayStatus | String (exact match) |
currency | String (exact match) |
amountCaptured | Number |
amountReversed | Number |
captureMethod | String (exact match) |
captureDelay | Number |
captureAt | Date |
statementDescriptor | String |
isCaptured | Boolean |
isReversed | Boolean |
isFullyReversed | Boolean |
isChargebacked | Boolean |
isRedirectRequired | Boolean |
initiator | String (exact match) |
offSessionType | String (exact match) |
latestTransaction | String (exact match) |
latestTransaction.processorId | String (eq and in only) |
latestTransaction.gatewayProfile.id | String (exact match) |
latestTransaction.gatewayProfile.title | String |
customer | String (exact match) |
customer.email | String |
paymentMethod | String (exact match) |
paymentMethod.type | String (exact match) |
paymentMethod.card.lastFour | String (exact match) |
paymentMethod.card.bin | String (exact match), plus a BIN range |
paymentMethod.card.brand | String (exact match) |
paymentMethod.card.expYear | String (exact match) |
paymentMethod.card.expMonth | String (exact match) |
paymentMethodOptions.card.gatewayProfile | String (exact match) |
paymentMethodOptions.card.cascade | String (exact match) |
paymentMethodOptions.card.mode | String (exact match) |
paymentMethodOptions.paypal.gatewayProfile | String (exact match) |
product.name | String |
metadata | JSON |
product | String (exact match) |
latestDeclineType | String (exact match) |
Prices
Endpoint: GET /v2/prices
Sortable fields: id, createdAt, updatedAt, amount, type
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
amount | Number |
currency | String (exact match) |
type | String (exact match) |
name | String |
product | String (exact match) |
product.name | String |
active | Boolean |
metadata | JSON |
Products
Endpoint: GET /v2/products
Sortable fields: id, createdAt, updatedAt, name, active
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
name | String |
active | Boolean |
shippable | Boolean |
category | String (exact match) |
sku | String |
metadata | JSON |
Secret Keys
Endpoint: GET /v2/secret-keys
Sortable fields: id, createdAt, updatedAt
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
active | Boolean |
Subscriptions
Endpoint: GET /v2/subscriptions
Sortable fields: id, createdAt, updatedAt, status, currentPeriodStart, currentPeriodEnd, startDate, endDate
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
status | String (exact match) |
startDate | Date |
endDate | Date |
autoBillingEnabled | Boolean |
autoBillingDisabledAt | Date |
autoBillingDisabledReason | String (exact match) |
cancelAtPeriodEnd | Boolean |
cancelAtCycle | Number |
currentPeriodStart | Date |
currentPeriodEnd | Date |
currentCycle | Number |
metadata | JSON |
customer | String (exact match) |
customer.email | String |
price | String (exact match) |
price.product | String (exact match) |
setupInvoice.payment | String (exact match) |
price.product.name | String |
upcomingInvoice.periodStart | Date |
invoices.payment.retry.nextAt | Date |
Transactions
Endpoint: GET /v2/transactions
Sortable fields: id, createdAt, updatedAt, amount, status
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
amount | Number |
currency | String (exact match) |
status | String (exact match) |
processorId | String (eq and in only) |
paymentMethod | String (exact match) |
payment | String (exact match) |
gatewayProfile | String (exact match) |
customer | String (exact match) |
execution | String (exact match) |
paymentMethod.card.bin | String (exact match) |
paymentMethod.card.brand | String (exact match) |
Webhook Subscribers
Endpoint: GET /v2/webhook-subscribers
Sortable fields: id, createdAt, updatedAt
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
updatedAt | Date |
Webhook deliveries
Endpoint: GET /v2/webhook-deliveries
Sortable fields: id, createdAt, firstDispatchedAt, lastDispatchedAt, abandonedAt, attempts
Filterable fields:
| Field | Data Type |
|---|---|
id | String (exact match) |
createdAt | Date |
firstDispatchedAt | Date |
lastDispatchedAt | Date |
abandonedAt | Date |
success | Boolean |
attempts | Number |
latestResponseStatus | Number |
event.type | String (exact match) |
event | String (exact match) |
subscriber | String (exact match) |