Pagination & filtering
How list and search endpoints page, sort, and filter — the pagination block, app-API Pageable params, admin sortBy/order/paginate params, and unpaginated lists.
List and search endpoints return their rows in data and describe the slice you
got back in a pagination block on the response envelope. There are two
parameter styles depending on the audience:
- App API endpoints (
X-Api-Key/X-Api-Secret) use Spring'sPageablebinding:page,size, andsort=field,direction. - Admin API endpoints (
Authorization: Bearer …) use an explicitPaginationRequest:page,size,sortBy,order,paginate.
They differ only at the edge — both feed the same pagination block back to
you. This page covers both, plus filtering and the handful of endpoints that
return a plain, unpaginated list.
Every response uses the standard envelope:
{
"data": [],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:00:00Z" },
"pagination": { "page": 0, "size": 20, "totalElements": 42, "totalPages": 3 }
}The pagination block
When an endpoint is paged, the envelope carries a pagination object alongside
data. It is built from the underlying Spring Page.
| Field | Type | Notes |
|---|---|---|
page | integer | The zero-indexed page number you got back. The first page is 0, not 1. |
size | integer | The page size (rows per page). For an unpaged result this is the total row count. |
totalElements | integer (long) | Total number of rows matching the filters across all pages. |
totalPages | integer | Total number of pages at the current size. 1 for an unpaged result. |
Pages are zero-indexed. To fetch the second page, send page=1. Iterate
until page reaches totalPages - 1.
Endpoints that return a plain list (not a page) set pagination to null —
see Unpaginated lists below.
App vs admin: two inputs, one output
The sort syntax is not the same on both sides. App API endpoints take a
single combined sort=field,direction parameter (Spring's convention). Admin
endpoints take two parameters, sortBy=field and order=ASC|DESC. Sending
sortBy/order to an app endpoint, or sort=field,dir to an admin endpoint,
is silently ignored — the wrong parameter just falls back to the default.
App API list endpoints
App-facing list endpoints (such as GET /products) bind their paging straight
from Spring's Pageable, so they accept these query parameters:
| Param | Type | Required | Notes |
|---|---|---|---|
page | integer | No | Zero-indexed page number. Defaults to 0. |
size | integer | No | Rows per page. Defaults to 20. |
sort | string | No | field,direction — e.g. sort=createdAt,desc. Direction is asc or desc (case-insensitive); omit it to default to ascending. Repeat the param for multi-field sorts (sort=status,asc&sort=createdAt,desc). |
The mode and app_id are derived from the credential, so
results are already scoped to your app and mode — you never page across another
app's data (app isolation).
The mode (TEST or LIVE) is derived from the API credential that authenticated the request — never from the request body.
Example: list products, newest first
curl "http://localhost:8080/api/v1/products?status=ACTIVE&sort=createdAt,desc&page=0&size=20" \
-H "X-Api-Key: oi_test_8a1f2c4e6b90" \
-H "X-Api-Secret: sk_test_3d7e9f0a2c41bf68"{
"data": [
{ "id": 5012, "name": "Pro plan", "status": "ACTIVE" },
{ "id": 5009, "name": "Starter plan", "status": "ACTIVE" }
],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:00:00Z" },
"pagination": { "page": 0, "size": 20, "totalElements": 2, "totalPages": 1 }
}Admin API list & search endpoints
Admin search endpoints (such as GET /admin/invoices) take an explicit set of
paging parameters, each with a server-side default. They map to a
PaginationRequest and then to a Spring Pageable.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
page | integer | No | 0 | Zero-indexed page number. |
size | integer | No | 20 | Rows per page. |
sortBy | string | No | createdAt | Entity field to sort on. |
order | enum ASC | DESC | No | DESC | Sort direction. |
paginate | boolean | No | true | false returns the full collection unpaged — see below. |
Admin endpoints are session-authenticated and span apps and modes, so they also
expose filters that the app API does not (for example an optional mode filter,
and a cross-app applicationId). See Filtering.
Example: search invoices
curl "http://localhost:8080/api/v1/admin/invoices?page=0&size=20&sortBy=createdAt&order=DESC&paginate=true&status=OVERDUE" \
-H "Authorization: Bearer sess_4f8c1b9d2e7a6054"{
"data": [
{ "id": 8801, "number": "INV-2026-000812", "status": "OVERDUE", "totalMinor": 150000 },
{ "id": 8794, "number": "INV-2026-000805", "status": "OVERDUE", "totalMinor": 49900 }
],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:00:00Z" },
"pagination": { "page": 0, "size": 20, "totalElements": 37, "totalPages": 2 }
}totalMinor is in integer minor units: 150000 means
1,500.00 BDT.
Opting out of paging
On admin endpoints, set paginate=false to get the entire matching
collection in one shot. Internally this maps the request to Pageable.unpaged(),
so page, size, sortBy, and order are ignored. Use it only for small,
bounded result sets (e.g. exporting a short filtered list) — there is no upper
bound, so a broad filter can return a large payload.
The response still includes a pagination block, but it describes a single page
holding every matching row: page is 0, totalPages is 1, and size
equals the number of rows returned.
curl "http://localhost:8080/api/v1/admin/invoices?paginate=false&status=DRAFT" \
-H "Authorization: Bearer sess_4f8c1b9d2e7a6054"{
"data": [
{ "id": 8702, "number": "INV-2026-000700", "status": "DRAFT", "totalMinor": 25000 },
{ "id": 8703, "number": "INV-2026-000701", "status": "DRAFT", "totalMinor": 80000 },
{ "id": 8711, "number": "INV-2026-000709", "status": "DRAFT", "totalMinor": 12500 }
],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:00:00Z" },
"pagination": { "page": 0, "size": 3, "totalElements": 3, "totalPages": 1 }
}Filtering
Filters are per-endpoint — each list/search endpoint exposes the filters that make sense for its resource, and unknown filter params are ignored. They combine with the paging params above. Common filters you will encounter:
| Filter | Type | Where | Notes |
|---|---|---|---|
status | enum | App & admin lists | Narrow by resource status, e.g. status=ACTIVE (products), status=OVERDUE (invoices). |
active | boolean | Some app lists | Toggle active-only rows (e.g. prices). |
customerReference | string | App payment list | The app's own customer reference — see Unpaginated lists. |
search / query | string | Admin search | Free-text match (e.g. customer name/email). |
number | string | Admin invoice search | Match by invoice number. |
customerRefId | integer | Admin search | Filter by a resolved customer reference id. |
from / to | date (YYYY-MM-DD) | Admin search | Inclusive-from, exclusive-to created-at window. |
applicationId | integer | Admin search | Cross-app filter (admin only). |
mode | enum TEST | LIVE | Admin search | Optional filter on admin search endpoints only. |
On app API endpoints there is no mode filter — the mode is fixed by your
credential. The optional mode filter exists only on admin search endpoints,
which are session-authenticated and span both modes. Separately, the admin
dashboard analytics endpoints take mode as
a required query parameter — that is the single documented exception to the
"mode comes from the credential" rule.
For the exact filter set, types, and allowed enum values of each endpoint, see the generated API reference.
Unpaginated lists
A few endpoints intentionally return a plain list, not a page. They are
scoped tightly enough by a required filter that paging adds no value, so they set
pagination to null.
The canonical example is the app payments list, GET /payments, which requires
a customerReference query parameter and returns that customer's payments newest
first:
| Param | Type | Required | Notes |
|---|---|---|---|
customerReference | string | Yes | The app's own reference for the customer. Omitting it is a 400. |
curl "http://localhost:8080/api/v1/payments?customerReference=cust_7f3a91" \
-H "X-Api-Key: oi_test_8a1f2c4e6b90" \
-H "X-Api-Secret: sk_test_3d7e9f0a2c41bf68"{
"data": [
{ "id": 4410, "reference": "pay_2c9a", "status": "SUCCEEDED", "amountMinor": 150000, "customerReference": "cust_7f3a91" },
{ "id": 4388, "reference": "pay_1b7e", "status": "FAILED", "amountMinor": 150000, "customerReference": "cust_7f3a91" }
],
"meta": { "success": true, "message": null, "errorCode": null, "timestamp": "2026-06-30T12:00:00Z" },
"pagination": null
}When pagination is null, data already holds the complete result for the
given filter — there are no further pages to fetch.
Related
Responses & errors
The full envelope shape, meta block, and error codes.
Test & live modes
Why list results are scoped by the mode of your credential.
App isolation
Every record carries app_id + mode; you never page across another app.
Dashboard analytics
The admin endpoints that take mode as a required parameter.
API reference
Exact per-endpoint params, filters, and enum values.