Pagination, Filtering, and Sorting
The Merchant Data API uses cursor-based pagination for all list queries (payins, payouts, claims). This guide explains how to paginate through results, apply filters, and understand the response structure.
Most list queries return results in pages using forward-only cursor pagination. This
guide explains how to page through results, filter them, and understand the fixed sort
order.
Pagination
The payins, payouts, claims, and movementBalances queries are paginated. You
control paging with two arguments:
| Argument | Type | Description |
|---|---|---|
first | Int | The maximum number of items to return. Defaults to 1000, capped at 5000. |
after | String | An opaque cursor from a previous page. Returns items after that cursor. |
Each paginated response contains:
edges— a list of{ cursor, node }pairs. Thenodeholds the record.pageInfo.hasNextPage—trueif more items exist after this page.pageInfo.endCursor— the cursor of the last edge. Pass it asafterto fetch the
next page.summary.rowCount— the number of rows in this page.summary.pageSizeLimit— the effective page size applied.summary.totalCount— the total number of matching rows, when available.
First page
Request
curl -X POST https://api.payretailers.com/data-api/graphql \
-u "YOUR_SHOP_ID:YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query Payins($first: Int, $after: String, $filter: PayinFilterInput) { payins(first: $first, after: $after, filter: $filter) { edges { cursor node { transactionId amount currency createdTs } } pageInfo { hasNextPage endCursor } summary { rowCount pageSizeLimit totalCount } } }",
"variables": {
"first": 100,
"after": null,
"filter": { "createdDateFrom": "2026-06-01", "createdDateTo": "2026-06-30" }
},
"operationName": "Payins"
}'Response (truncated)
{
"data": {
"payins": {
"edges": [
{
"cursor": "eyJ0IjoiMjAyNi0wNi0wMSJ9",
"node": {
"transactionId": "PI-9000001",
"amount": "125.50",
"currency": "BRL",
"createdTs": "2026-06-01T09:12:00Z"
}
}
],
"pageInfo": {
"hasNextPage": true,
"endCursor": "eyJ0IjoiMjAyNi0wNi0wMiJ9"
},
"summary": {
"rowCount": 100,
"pageSizeLimit": 100,
"totalCount": 1284
}
}
}
}Next page
Pass the previous page's endCursor as after, keeping the same filter:
{
"variables": {
"first": 100,
"after": "eyJ0IjoiMjAyNi0wNi0wMiJ9",
"filter": { "createdDateFrom": "2026-06-01", "createdDateTo": "2026-06-30" }
}
}Repeat until pageInfo.hasNextPage is false.
Cursor rules
- Cursors are opaque. Do not parse, modify, truncate, or construct them yourself.
- Cursors expire after 4 hours. An expired cursor returns
CURSOR_EXPIRED; start
again from the first page. - A malformed cursor returns
INVALID_CURSOR. Pass the exactendCursorvalue you
received. - Pagination is forward-only. There is no backward paging (
last/before).
Non-paginated queries
reportBalancesis not paginated. It returns{ items }only, with nopageInfo
orsummary. Bound the result withcreatedDateFrom/createdDateTo(default and
maximum 90 days).aggregationsreturns a single result object, not a page.
Filtering
Each list query accepts a filter input. All filter fields are optional. Ranges use
...From / ...To pairs; identifiers are exact-match.
Common filter patterns
| Filter type | Fields | Behaviour |
|---|---|---|
| Date range | createdDateFrom, createdDateTo | Bounds the governed date window. |
| Amount range | amountFrom, amountTo | Inclusive numeric range. |
| Status | status | Exact match on the status label. |
| Exact identifier | transactionId, trackingId, claimId, operationId | Pinpoint lookup (see below). |
Available filter inputs per query:
PayinFilterInput:transactionId,trackingId,shopId,status,personId,
customerEmail,countryName,paymentMethodName,amountFrom,amountTo,
createdDateFrom,createdDateTo,editedDateFrom,editedDateTo.PayoutFilterInput: the payin fields (minuspaymentMethodName) plus
settlementAmountFrom,settlementAmountTo.ClaimFilterInput:claimId,transactionId,shopId,status,claimReason,
amountFrom,amountTo,refundedAmountFrom,refundedAmountTo,createdDateFrom,
createdDateTo,processedDateFrom,processedDateTo.MovementBalanceFilterInput:shopId,movementType,operationId,
createdDateFrom,createdDateTo.ReportBalanceFilterInput:shopId,createdDateFrom,createdDateTo.AggregationFilterInput:transactionId,shopId,status,personId,
createdDateFrom,createdDateTo.
Pinpoint filters
Filtering by a unique identifier is a pinpoint filter: it fetches a specific record
and bypasses the default date-window requirement. Pinpoint fields are transactionId,
trackingId, claimId, operationId, and personId.
For example, to fetch a single claim by ID, combine a pinpoint filter with first: 1:
curl -X POST https://api.payretailers.com/data-api/graphql \
-u "YOUR_SHOP_ID:YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query ClaimById($filter: ClaimFilterInput) { claims(first: 1, filter: $filter) { edges { node { claimId transactionId amount status claimReason refundedAmount } } } }",
"variables": { "filter": { "claimId": "CLM-100045" } },
"operationName": "ClaimById"
}'Date windows
Date filters are governed to protect the platform:
| Dataset | Default window | Maximum window |
|---|---|---|
| Payins, payouts, claims | 90 days | 90 days |
| Report balances | 90 days | 90 days |
| Movement balances | 1 day | 31 days |
If you omit a date filter on a governed query, the default window is applied
automatically (unless you use a pinpoint filter). Requesting a wider window returns
DATE_WINDOW_EXCEEDED; a range where "from" is after "to" returns INVALID_DATE_RANGE.
Split longer ranges into several queries.
Sorting
Sort order is fixed and not configurable. Results are returned in a stable order
keyed on each dataset's cursor key plus its creation timestamp:
| Dataset | Cursor key |
|---|---|
| Payins, payouts | transactionId |
| Claims | claimId |
| Movement balances | operationId |
This fixed order is what makes cursor pagination stable across pages.
Next steps
- Error handling — governance and cursor errors.
- Rate limits — page size and concurrency limits.
- Data model reference — filterable fields per type.
Updated 23 days ago