How Do You Get Bank Transaction Data via REST API?
Z
Editorial Team
Content Writer
Last Updated: 25 October 2026
Quick Summary
You get bank transaction data via REST API by calling an endpoint that returns booked transactions as a paginated JSON collection, then storing those records so your product can query them. Each record carries a stable transactionId, bookingDate, valueDate, signed amount, currency and counterparty details. Authentication runs through the bank's PSD2 consent flow, with short-lived access tokens tied to that consent.
Book on bookingDate and report on valueDate to avoid reconciliation drift.
Loop until the continuation marker is absent, not until a page looks short.
Deduplicate on transactionId rather than on amount and date.
Read the pending array as well as booked, or balances will disagree.
Zenith Bank Sync covers 2,400+ banks across 30 European countries via one API.
What a REST API for Bank Transaction Data Actually Returns
A bank transactions API returns a structured JSON record for every movement on an account: amount, currency, booking date, value date, counterparty name, and a unique transaction identifier. That record is the raw material for reconciliation, cash forecasting, and spend analysis. Getting bank transaction data via REST API means calling an endpoint that returns these records as a paginated collection, then storing them in a form your product can query.
Keeping thousands of transactions consistent across 2,400+ banks in 30 European countries is where integrations fail.
The distinction matters because most tutorials stop at the first successful 200 OK. Below, we'll show you exactly what each field means, how consent and tokens actually behave, and the mistakes that cause missing rows in production.
The fields behind a booked transaction
A booked transaction is one the bank has posted to the account, as opposed to a pending authorisation. The difference decides whether your ledger can trust the row.
transactionId - the bank's stable identifier. Store it as your idempotency key.
bookingDate - when the bank posted the entry. This is your accounting date.
valueDate - when funds actually move. Interest and cash positioning depend on it.
amount and currency - signed decimal and ISO 4217 code.
creditorName / debtorName - counterparty, often truncated by the bank.
remittanceInformation - the payment reference. Free text and frequently inconsistent.
Watch out: treating valueDate as your ledger date is the single most common cause of reconciliation drift. Book on bookingDate, report on valueDate.
How Bank API Authentication Works Under PSD2
Bank API authentication under PSD2 is a three-party flow: your application, the customer's bank, and a regulated account information service provider. The customer grants explicit consent at their own bank, the bank issues tokens tied to that consent, and every subsequent call carries those tokens. You never see or store the customer's banking credentials.
This is the part developers underestimate. The European Banking Authority's PSD2 guidance sets the framework, but each bank implements its own consent screens, token lifetimes, and error codes.
Consent has a clock on it. Under PSD2, account information consent is time-limited and must be re-confirmed periodically, with the exact duration set by the bank and national regulator. Do not hardcode a refresh interval; read it from the consent response and schedule renewal well before expiry.
Access tokens are short-lived, often minutes rather than hours. Refresh tokens carry the consent. The practical pattern:
Exchange the authorisation code for an access and refresh token pair.
Cache the access token with its expiry minus a safety margin.
Refresh proactively, not reactively. A failed refresh mid-reconciliation is painful.
Handle 401 by refreshing once, then surfacing a re-consent prompt to the user.
If you're building this from scratch, our guide to PSD2 API integration for startups walks through the full consent and token lifecycle.
Planning Your Open Banking API Integration
Open Banking API integration succeeds or fails on decisions made before the first line of code. Three choices determine most of the outcome: whether you build against banks directly or through an aggregator, how you model transactions in your own database, and how you handle re-consent at scale.
Building direct means an AISP licence, eIDAS certificates, and a separate integration for every bank. For a product team, that is a multi-quarter project with ongoing maintenance. Aggregation through a provider collapses that into one API surface. If you are still weighing the two approaches, our open banking vs scraping comparison gives finance teams and developers a clear verdict.
Model transactions before you fetch them. Decide your primary key, your deduplication rule, and whether you store raw payloads alongside normalised rows. We would always keep the raw JSON: when a bank changes a field, you can re-derive without re-fetching.
Best for: product teams shipping bank connectivity as a feature, not a business.
Skip if: you are a licensed AISP whose differentiation is direct connectivity. (Source: challenges of cross-border financial data integration)
Making Your First Call to a Bank Transactions API
Your first call should be a read of one account over a narrow date range. Start small, confirm the shape of the response, then widen. The request below follows the common European pattern: a bearer token, an account identifier, and date bounds. Maintaining consistent access requires diligent tracking API keys to ensure your integration remains authenticated as security credentials rotate.
GET /accounts/{accountId}/transactions?dateFrom=2026-09-01&dateTo=2026-09-30
Authorization: Bearer {access_token}
Accept: application/json
A successful response returns a booked-transactions array plus a continuation marker:
{
"transactions": {
"booked": [
{
"transactionId": "20260915-4471",
"bookingDate": "2026-09-15",
"valueDate": "2026-09-15",
"amount": "-248.50",
"currency": "EUR",
"creditorName": "Northgate Supplies Ltd",
"remittanceInformation": "INV 88213"
}
],
"pending": []
},
"continuationKey": null
}
A developer at a desk with two monitors, one showing a code editor with an API request and the other showing a JSON response, notebook and coffee beside the keyboard
### A worked request and response
Read the response in this order: confirm `continuationKey` is null before assuming you have everything, check the `pending` array separately, and validate that `amount` is signed. A negative amount is a debit. Getting the sign convention wrong silently inverts every cash report downstream.
## Handling Bank Transaction API Pagination Without Missing Rows
Bank transaction API pagination is where silent data loss lives. Banks return results in pages, and the mechanism varies: some use a continuation key, some use a link header, some use offset and limit. Miss the loop termination condition and you truncate the statement without an error.
The rules that prevent missing rows:
- Loop until the continuation marker is absent, never until a page returns fewer items than the limit.
- Never assume page size is stable. Banks cap it differently per endpoint.
- Fetch by `bookingDate` windows and overlap them by a day to catch late bookings.
- Persist the continuation key between runs so a crash does not restart the whole statement.
- Deduplicate on `transactionId`, not on amount and date.
Watch out: paginating while new transactions post can shift results between pages. Date-windowed fetching with a one-day overlap is more reliable than deep pagination on a live account.
## Common Mistakes When Pulling Transaction Data
The mistakes that break bank transaction integrations are rarely exotic. They are ordinary assumptions that hold in testing and collapse in production.
**Assuming consent is permanent.** It is not. Consent expires, and a re-consent prompt is a product requirement, not an edge case. Build the UI for it on day one.
**Ignoring the pending array.** Pending transactions become booked ones. If you only read `booked`, your balance will disagree with the customer's app.
**Storing tokens in the database in plaintext.** Refresh tokens are long-lived credentials. Encrypt them at rest and scope access tightly.
**Hardcoding bank-specific behaviour.** Field names, date formats, and error codes differ across banks. Normalise at the edge, keep raw payloads, and never let a bank quirk leak into your domain model.
**Skipping idempotency.** Retries are normal with flaky bank endpoints. Without a stable key, you will double-count transactions.
A common mistake is testing against one bank and shipping to thirty. The [Berlin Group's NextGenPSD2 specification](https://berlin-group.org/standard/open-banking-nextgenpsd2/) is the closest thing to a common standard, but implementation variance across banks remains wide.
## Connecting Your First Account to Zenith Bank Sync
Zenith Bank Sync connects 2,400+ banks across 30 European countries through a single open banking API. You authenticate through your bank's own PSD2 consent flow, choose the accounts to sync, and Zenith keeps statements, balances, and transactions current. No bank integrations to build, no AISP licence, no eIDAS certificates.
For developers, the REST API returns normalised transaction data you can pipe into your own product. For finance teams, the same connection feeds Google Sheets, CSV, or an AI assistant over MCP. Coverage spans the EU, the UK, and the wider EEA, and pricing is per connected account per month with no platform fee, no minimum volume, and no annual contract.
Read-only access, GDPR compliance, and CASA T2 assurance mean the connection does not widen your data exposure. If you're ready to start, you can [connect a UK bank to Zenith](/bank-sync/gb) and have transactions flowing in minutes.
<section style="margin:3rem 0 2rem 0;" itemscope itemtype="https://schema.org/FAQPage"><h2 style="font-weight:700; margin:0 0 4px 0;">Frequently Asked Questions</h2><div style="padding:20px 0; border-bottom:1px solid #e5e7eb;" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question"><h3 style="font-weight:600; margin:0 0 8px 0;" itemprop="name">How can I get bank transaction data through a REST API?</h3><div style="line-height:1.7;" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer"><p itemprop="text" style="margin:0;">You connect the account through your bank's PSD2 consent flow, exchange the returned authorisation code for an access token, then call the account's transactions endpoint with that token. The response is JSON containing booked and pending entries with amounts, dates, counterparties and references. In practice most teams use an aggregator rather than building a direct connection to each bank, because every bank exposes its own consent screens, token lifetimes and error formats.</p></div></div><div style="padding:20px 0; border-bottom:1px solid #e5e7eb;" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question"><h3 style="font-weight:600; margin:0 0 8px 0;" itemprop="name">What data can a bank transactions API return?</h3><div style="line-height:1.7;" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer"><p itemprop="text" style="margin:0;">A typical response gives you the booking date and value date, the amount and currency, credit or debit indicator, the counterparty name, and a remittance reference. Many banks also return a status flag separating booked from pending entries, a transaction ID for de-duplication, and a merchant category code where one exists. Balance endpoints sit alongside the transaction feed, so you can reconcile a running total against the entries you have pulled.</p></div></div><div style="padding:20px 0; border-bottom:1px solid #e5e7eb;" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question"><h3 style="font-weight:600; margin:0 0 8px 0;" itemprop="name">How do I handle pagination and transaction history in a bank API?</h3><div style="line-height:1.7;" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer"><p itemprop="text" style="margin:0;">Send a date range and a page size, read the continuation token or links object in the response, and request the next page until it comes back empty. Never assume the first page holds everything. Store the transaction ID of every row you keep so a re-run does not create duplicates, and set your sync window to overlap the last successful pull by a day or two to catch late-booked items.</p></div></div><div style="padding:20px 0; border-bottom:1px solid #e5e7eb;" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question"><h3 style="font-weight:600; margin:0 0 8px 0;" itemprop="name">How often does an Open Banking API update transaction data?</h3><div style="line-height:1.7;" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer"><p itemprop="text" style="margin:0;">Banks refresh account data on their own schedule, and PSD2 rules limit how often an aggregator may re-poll without the customer present. Most feeds land within a few hours of a transaction booking, but same-day pending items can shift. Build your sync to run on a fixed interval, compare the returned transaction IDs against what you already hold, and treat the feed as near-live rather than real-time.</p></div></div></section>
---
Getting bank transaction data via REST API is not the hard part. Keeping consent valid, pagination complete, and records deduplicated across dozens of banks is. That is the work Zenith removes. Connect an account, and we handle the consent lifecycle, the bank-by-bank variance, and the refresh cycles so your team builds product instead of plumbing. Start free with Zenith and get live bank transaction data into the tools you already use.
You connect the account through your bank's PSD2 consent flow, exchange the returned authorisation code for an access token, then call the account's transactions endpoint with that token. The response is JSON containing booked and pending entries with amounts, dates, counterparties and references. In practice most teams use an aggregator rather than building a direct connection to each bank, because every bank exposes its own consent screens, token lifetimes and error formats.
A typical response gives you the booking date and value date, the amount and currency, credit or debit indicator, the counterparty name, and a remittance reference. Many banks also return a status flag separating booked from pending entries, a transaction ID for de-duplication, and a merchant category code where one exists. Balance endpoints sit alongside the transaction feed, so you can reconcile a running total against the entries you have pulled.
Send a date range and a page size, read the continuation token or links object in the response, and request the next page until it comes back empty. Never assume the first page holds everything. Store the transaction ID of every row you keep so a re-run does not create duplicates, and set your sync window to overlap the last successful pull by a day or two to catch late-booked items.
Banks refresh account data on their own schedule, and PSD2 rules limit how often an aggregator may re-poll without the customer present. Most feeds land within a few hours of a transaction booking, but same-day pending items can shift. Build your sync to run on a fixed interval, compare the returned transaction IDs against what you already hold, and treat the feed as near-live rather than real-time.