This is v2.2-rc2 — a release candidate for the v2.2 standards, published for review and not yet ratified. It MUST NOT be used as the basis for a production implementation. For the current standards, switch to v2.1 using the version selector. See the v2.1 → v2.2-rc2 changelog for every change in this version.
Pagination 3 min read
Pagination for Bank Data Sharing endpoints is page-based on the LFI (Ozone Connect) side. The API Hub converts the LFI's page/meta response into the Links envelope returned to the TPP.
For the end-to-end picture — including how the Hub converts LFI meta into TPP Links — see Pagination — LFI meta to TPP Links.
Required vs optional pagination
| Endpoint | Pagination |
|---|---|
GET/accounts/{accountId}/transactions | Required |
GET/accounts/{accountId}/statements | Required |
GET/accounts | Optional |
GET/accounts/{accountId}/balances | Optional |
GET/accounts/{accountId}/beneficiaries | Optional |
GET/accounts/{accountId}/direct-debits | Optional |
GET/accounts/{accountId}/scheduled-payments | Optional |
GET/accounts/{accountId}/standing-orders | Optional |
GET/accounts/{accountId}/products | Optional |
GET/accounts/{accountId}/customer | Optional |
Transactions and statements span long history (at least two years) and routinely produce large result sets — pagination is required so responses remain bounded. For other list endpoints, LFIs MAY paginate where result sets warrant it, or return all matching records in a single response.
page / page-size
The Hub sends page and page-size as query parameters on every paginated request:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | 1-indexed page number |
page-size | integer | 100 | Number of records per page |
The LFI MUST return the slice of the result set that corresponds to page and page-size. Three steps, in that order: filtering (e.g. fromBookingDateTime / toBookingDateTime for transactions) is applied first, the filtered result set is then ordered, and the page is cut from the ordered set last. See Ordering and stability for the order transactions and statements MUST use.
meta — paginated, totalPages, totalRecords
The LFI indicates the pagination state in the response meta object:
| Field | Type | Description |
|---|---|---|
paginated | boolean | true if the response is paginated. false or omitted if the full result set is returned in a single response |
totalPages | integer | The total number of pages in the full result set, given the current page-size |
totalRecords | integer | The total number of records in the full result set across all pages |
totalPages and totalRecords MUST reflect the filtered result set — not the whole table. If a transaction query filters by date range, totalRecords is the count of transactions in that range, and totalPages is ceil(totalRecords / page-size).
Example — transactions, page 2 of 12
Request:
GET /accounts/acc-001/transactions?fromBookingDateTime=2026-01-01T00:00:00Z&page=2&page-size=100
Response:
{
"data": [
{ "accountId": "acc-001", "transactionId": "txn-900234", "...": "..." }
],
"meta": {
"paginated": true,
"totalPages": 12,
"totalRecords": 1187
}
}
The Hub uses totalPages to construct Links.First, Links.Prev, Links.Next, and Links.Last on the TPP-facing response, and surfaces totalPages in the TPP's Meta. See the Pagination KB article for the full conversion.
Walking pages end-to-end
The diagram below traces a three-page transactions query between the TPP, Hub, and your Ozone Connect endpoint. The TPP makes a single unparameterised request and follows Links.Next on each response; the Hub translates each call into a page / page-size request sent to your Ozone Connect GET /accounts/{accountId}/transactions endpoint and converts your meta back into the TPP's Links envelope.
Empty matches return 200, not 404
If filtering yields no records, return 200 with an empty data array and:
{
"data": [],
"meta": {
"paginated": true,
"totalPages": 0,
"totalRecords": 0
}
}
Do not return 404 for an empty filtered result.
Newest first, applied before the page is cut
From v2.2 the order is specified rather than left to the LFI. Transactions and statements MUST be returned newest first:
| Endpoint | Order (MUST) | Tiebreaker (SHOULD) |
|---|---|---|
GET/accounts/{accountId}/transactions | bookingDateTime descending | transactionId descending |
GET/accounts/{accountId}/statements | OpeningDate descending | StatementId descending |
Transactions order on bookingDateTime and tiebreak on transactionId, matching the camelCase used across most of Ozone Connect. The statement fields are OpeningDate and StatementId — PascalCase, as they appear in the statement schema. OpeningDate is also not the same field as StatementDate, which the schema carries separately.
The date field is a MUST and the tiebreaker a SHOULD, but apply both — the tiebreaker is what makes the order stable rather than merely correct. Transactions routinely share a bookingDateTime, and OpeningDate is a date rather than a timestamp, so ties are common on both endpoints. Ordering on the date alone lets two tied records swap places between one page request and the next, which can return the same record on two pages and omit another entirely.
The ordering MUST be applied to the filtered result set beforepage and page-size are applied — that is where the page is cut. Page 1 then always contains the most recent records matching the request, and a record does not move between pages while a TPP is paging through a result set.
fromBookingDateTime / toBookingDateTime and fromStatementDate / toStatementDate filter the result set. They have no effect on the order of what remains.
There is no sort parameter on either endpoint — a TPP cannot request ascending order, so the order above is the only one your Ozone Connect GET /accounts/{accountId}/transactions and GET /accounts/{accountId}/statements endpoints ever need to produce. If new records arrive between page requests, the LFI SHOULD ensure the same record is not returned on two different pages of the same logical query.
