OI Payments Docs
Core concepts

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's Pageable binding: page, size, and sort=field,direction.
  • Admin API endpoints (Authorization: Bearer …) use an explicit PaginationRequest: 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.

FieldTypeNotes
pageintegerThe zero-indexed page number you got back. The first page is 0, not 1.
sizeintegerThe page size (rows per page). For an unpaged result this is the total row count.
totalElementsinteger (long)Total number of rows matching the filters across all pages.
totalPagesintegerTotal 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:

ParamTypeRequiredNotes
pageintegerNoZero-indexed page number. Defaults to 0.
sizeintegerNoRows per page. Defaults to 20.
sortstringNofield,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.

ParamTypeRequiredDefaultNotes
pageintegerNo0Zero-indexed page number.
sizeintegerNo20Rows per page.
sortBystringNocreatedAtEntity field to sort on.
orderenum ASC | DESCNoDESCSort direction.
paginatebooleanNotruefalse 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:

FilterTypeWhereNotes
statusenumApp & admin listsNarrow by resource status, e.g. status=ACTIVE (products), status=OVERDUE (invoices).
activebooleanSome app listsToggle active-only rows (e.g. prices).
customerReferencestringApp payment listThe app's own customer reference — see Unpaginated lists.
search / querystringAdmin searchFree-text match (e.g. customer name/email).
numberstringAdmin invoice searchMatch by invoice number.
customerRefIdintegerAdmin searchFilter by a resolved customer reference id.
from / todate (YYYY-MM-DD)Admin searchInclusive-from, exclusive-to created-at window.
applicationIdintegerAdmin searchCross-app filter (admin only).
modeenum TEST | LIVEAdmin searchOptional 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:

ParamTypeRequiredNotes
customerReferencestringYesThe 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.

On this page