Using PSD2 in Practice — Querying Bank Data, Providers, and the Aggregator Layer
The previous post covered what PSD2 is and the regulatory machinery underneath it. This one is about actually using it — reading balances, pulling transaction history, initiating payments, and choosing which provider to build on.
The short answer on direct bank API access: don't. Unless you're a licensed payment institution with eIDAS certificates and the budget to maintain integrations across dozens of different bank API standards, you use an aggregator. Here's what that looks like.
The Aggregator Layer
Aggregators are licensed AISPs (and often PISPs) that have already done the hard work:
- Obtained AISP/PISP licences in each relevant jurisdiction
- Got eIDAS certificates
- Built and maintained integrations with hundreds of banks
- Normalised the wildly inconsistent response formats from Berlin Group, STET, UK Open Banking, and proprietary bank APIs
You talk to the aggregator's unified API. The aggregator talks to the bank. The user gets redirected to their bank for consent, and you get back clean, structured data.
The Universal Flow
Regardless of provider, the flow is roughly the same:
1. Your server creates a "requisition" or "session" via the aggregator API
2. Aggregator returns a consent redirect URL (pointing to the bank)
3. User follows the URL → lands at their bank
4. User authenticates with SCA (biometrics, OTP, etc.) and approves access
5. User is redirected back to your app
6. You poll the aggregator API for account list, balances, transactions
The aggregator handles the SCA redirect, the OAuth dance with the bank, and the token management. You get a normalised response that looks the same whether the bank is Deutsche Bank or Monzo.
GoCardless Bank Account Data (formerly Nordigen)
GoCardless acquired Nordigen in 2022. The Bank Account Data product is the most developer-friendly of the major providers — free tier with generous limits, well-documented API, and a sandbox environment that works without any real bank connections.
Coverage: 2,500+ banks across 31 European countries.
Pricing: Free for up to 50 requisitions/month; paid plans for production volume.
Licence: Registered AISP. Read-only — no payment initiation.
Full walkthrough: read transactions from a bank account
Step 1 — Exchange credentials for tokens
curl -X POST https://bankaccountdata.gocardless.com/api/v2/token/new/ \
-H "Content-Type: application/json" \
-d '{"secret_id": "YOUR_SECRET_ID", "secret_key": "YOUR_SECRET_KEY"}'
{
"access": "eyJ...",
"access_expires": 86400,
"refresh": "eyJ...",
"refresh_expires": 2592000
}
Store the refresh token. Use the access token (valid 24h) for all subsequent calls.
Step 2 — Find the institution ID
curl https://bankaccountdata.gocardless.com/api/v2/institutions/?country=de \
-H "Authorization: Bearer ACCESS_TOKEN"
Returns a list with IDs like DEUTSCHEBANK_DEUTDEDB, N26_NTSBDEB1, REVOLUT_REVOLT21, etc. Each entry includes transaction_total_days — how many days of history the bank exposes (ranges from 90 to 730 depending on the bank).
Step 3 — Create an end-user agreement (optional)
If you need non-default settings (e.g. 180 days history, or a shorter access window):
curl -X POST https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/ \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"institution_id": "N26_NTSBDEB1",
"max_historical_days": 180,
"access_valid_for_days": 30,
"access_scope": ["balances", "details", "transactions"]
}'
Without this step, defaults apply: 90 days history, 90 days access, all scopes.
Step 4 — Create a requisition (get the consent link)
curl -X POST https://bankaccountdata.gocardless.com/api/v2/requisitions/ \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"redirect": "https://yourapp.com/callback",
"institution_id": "N26_NTSBDEB1",
"agreement": "AGREEMENT_ID",
"reference": "user-internal-id-123"
}'
Response includes a link — send the user here. They authenticate with N26, approve access, get redirected back to your callback URL.
Step 5 — List accounts
curl https://bankaccountdata.gocardless.com/api/v2/requisitions/REQUISITION_ID/ \
-H "Authorization: Bearer ACCESS_TOKEN"
{
"status": "LN",
"accounts": [
"065da497-e6af-4950-88ed-2edbc0577d20",
"bc6d7bbb-a7d8-487e-876e-a887dcfeea3d"
]
}
Status LN = Linked. CR = Created (user hasn't authenticated yet).
Step 6 — Read balances
curl https://bankaccountdata.gocardless.com/api/v2/accounts/065da497.../balances/ \
-H "Authorization: Bearer ACCESS_TOKEN"
{
"balances": [
{
"balanceAmount": { "amount": "1523.47", "currency": "EUR" },
"balanceType": "closingBooked",
"referenceDate": "2026-02-24"
},
{
"balanceAmount": { "amount": "1498.47", "currency": "EUR" },
"balanceType": "expected"
}
]
}
Balance types vary by bank. closingBooked = end of day settled balance; expected = includes pending transactions.
Step 7 — Read transactions
curl "https://bankaccountdata.gocardless.com/api/v2/accounts/065da497.../transactions/?date_from=2026-01-01" \
-H "Authorization: Bearer ACCESS_TOKEN"
{
"transactions": {
"booked": [
{
"transactionId": "2026012200481920-1",
"creditorName": "Lidl",
"transactionAmount": { "amount": "-34.50", "currency": "EUR" },
"bookingDate": "2026-01-22",
"valueDate": "2026-01-22",
"remittanceInformationUnstructured": "LIDL BERLIN 2024-01-21"
}
],
"pending": [
{
"transactionAmount": { "amount": "-12.99", "currency": "EUR" },
"valueDate": "2026-02-25",
"remittanceInformationUnstructured": "Spotify"
}
]
}
}
Note the inconsistency caveat in GoCardless's own docs: some banks include creditorName, some don't. Some include IBAN of the counterparty, some don't. Real-world bank data is messier than the spec.
Tink (Visa)
Tink is a Swedish open banking platform acquired by Visa in 2022 for €1.8 billion — which tells you something about the strategic importance of this space. It's the most enterprise-grade of the major providers.
Coverage: 3,400+ banks across 18 European countries.
Pricing: Paid; no meaningful free tier. Targeted at larger companies.
Licence: Licensed AISP and PISP across the EU.
Distinguishing features:
- Data enrichment — transaction categorisation, merchant identification, and financial insights as part of the offering; not just raw bank data
- Payment initiation — full PISP capability, enabling account-to-account payments (A2A)
- Income verification — analysed income signals from transaction history, used for lending decisioning
- Risk insights — affordability scoring, spending pattern analysis
Tink's API is more opinionated and higher-abstraction than GoCardless. Instead of raw Berlin Group fields, you get normalised transaction objects with enriched metadata. The trade-off is less access to raw bank data, more ready-to-use business logic.
Their authentication uses standard OAuth 2.0. The consent flow can be embedded as a widget (Tink Link) rather than requiring a full-page redirect — useful for mobile-first apps.
Salt Edge
A Moldovan-founded company, now based in London with EU regulatory presence. Strong AISP/PISP coverage and a notably wider global reach than the European-focused competitors.
Coverage: 5,000+ financial institutions across 50+ countries. Covers non-EU markets including the US, Canada, Australia, and Middle East — useful if you need global coverage from one provider.
Pricing: Paid with usage-based tiers.
Licence: AISP licensed, PSD2 compliant.
Distinguishing features:
- Partner Program — lets companies access EU bank data via Salt Edge without their own PSD2 licence or eIDAS certificates. Salt Edge acts as the licensed AISP, you consume their API under a partnership agreement.
- Data enrichment — transaction categorisation, merchant ID, and financial insights similar to Tink
- Broader account types — beyond payment accounts: savings, mortgages, investments, loyalty accounts (where banks expose these)
- Strong focus on lending and KYC use cases — income verification, affordability assessment, identity validation from bank data
Salt Edge is a good choice if you need to go beyond Europe or want an all-in-one enrichment layer without building your own categorisation engine.
Plaid
Primarily a US product, but with an EU presence. Plaid is best known for powering US fintech (they're behind the bank connection in most major US financial apps). Their EU coverage is narrower than the European-native providers.
Coverage: 12,000+ institutions globally, with meaningful coverage in UK, France, Germany, Netherlands, Spain, and Ireland in the EU.
Pricing: Paid, consumption-based.
Licence: Registered as an AISP in the UK (FCA) and in relevant EU countries.
Distinguishing features:
- Transaction categorisation — very mature, originally trained on US financial data; EU categorisation is good but not as deep
- Identity verification — account holder name and address from bank data
- Assets — snapshot-in-time report of account balances, suitable for mortgage applications
- Strong US/UK developer experience — if your audience is partly US-based and you want one SDK across geographies
If you're building something Europe-only, GoCardless or Tink will give you better coverage and a more straightforward compliance path. Plaid makes sense when you need both sides of the Atlantic from one integration.
Provider Comparison
| GoCardless | Tink | Salt Edge | Plaid | |
|---|---|---|---|---|
| EU banks | 2,500+ | 3,400+ | 5,000+ | ~500+ |
| Countries (EU) | 31 | 18 | 50+ (incl. non-EU) | 6 key markets |
| Free tier | ✅ (50 req/month) | ❌ | ❌ | ❌ |
| AISP | ✅ | ✅ | ✅ | ✅ |
| PISP (payment initiation) | ❌ | ✅ | ✅ | ❌ |
| Data enrichment | Basic | Advanced | Advanced | Advanced |
| Partner programme (no licence needed) | ✅ | ✅ | ✅ | ✅ |
| Historical data depth | Up to 730 days (bank-dependent) | Varies | Varies | Varies |
| Sandbox | ✅ (free) | ✅ | ✅ | ✅ |
The Payment Initiation Path (PISP)
If you want to move money — not just read data — you need a PISP-capable provider. Tink and Salt Edge both offer this.
A typical payment initiation flow via Tink:
1. Your server creates a payment initiation request:
POST /v1/payments/sepa/credit-transfers
{
"amount": { "value": 100.00, "currency": "EUR" },
"creditor": {
"name": "Acme GmbH",
"account": { "iban": "DE89370400440532013000" }
},
"remittanceInformation": "Invoice 2026-042"
}
2. Tink returns a redirect URL to the user's bank
3. User authenticates at their bank, approves the payment
4. Bank executes the SEPA Credit Transfer
5. Your webhook receives the payment status update
The payment lands as a standard SEPA Credit Transfer on the recipient's side — they can't tell it was initiated via API rather than their own banking app.
What the Data Actually Looks Like
A few things to know before building on transaction data:
Field availability varies wildly. The Berlin Group spec defines optional fields like creditorName, debtorIBAN, and remittanceInformationStructured. Whether a bank actually populates them depends on the bank, the account type, and sometimes the transaction type. You'll get rich data from Monzo and N26; you'll get sparse data from some traditional banks.
Transaction IDs are not reliable. Some banks change transaction IDs between API calls, making deduplication harder than it should be. Build your deduplication on a combination of bookingDate + amount + remittanceInformation if transactionId proves unstable.
Pending vs booked. The pending array contains pre-authorised transactions that haven't settled yet. They may never settle (authorisation cancelled), may settle at a different amount (e.g., petrol station holds), or may disappear and reappear in booked. Don't write business logic that depends on pending accuracy.
Timezones. bookingDate is a date string with no timezone. bookingDateTime (where available) includes an offset, but not all banks provide it.
Balance types. closingBooked (settled EOD balance) and expected (including pending) are the most common. Some banks also expose interimAvailable (real-time available balance including credit limits). For cash flow visibility, expected is usually what you want.
Practical Architecture
A reasonable server-side architecture for building on PSD2 account data:
User authorises via GoCardless redirect
↓
Store: requisition_id + account_ids in your DB
↓
Cron (every 4-6 hours): pull transactions + balances
↓
Deduplicate against existing records
↓
Write to local DB (Postgres, SQLite)
↓
Serve from your own DB — don't hit the aggregator on every user request
Why cache locally:
- Aggregator rate limits (typically per-account, per-day)
- Latency — bank API calls via aggregator can take 2-10 seconds
- Access window expires — once the 90-day consent lapses, you still have history
- Cost — some providers charge per API call
The aggregator is a sync source, not a real-time database. Treat it like a webhook source and store locally.
Licensing Shortcuts
If you want to experiment without a licence:
- GoCardless free tier — sandbox works immediately, production works with a free account up to 50 requisitions/month. No licence needed; GoCardless operates as the licensed AISP.
- Salt Edge Partner Programme — same model; Salt Edge is the AISP, you use their data under agreement.
- Tink Link — similar structure; Tink is the regulated entity.
When you exceed the free tier or want to offer payment initiation, you'll need to either get your own licence or negotiate a commercial agreement with the provider.
Where This Is Going
PSD2's legacy is the concept, even if the execution is messy. The coming wave is Open Finance under PSD3/FIDA — extending the same mandatory API access to investment accounts, pensions, insurance, and mortgages. The same aggregator layer will extend to cover those when the regulation lands.
The messiness of the current landscape — multiple standards, inconsistent data quality, per-bank quirks — is being addressed both by PSD3 standardisation efforts and by the aggregators building increasingly sophisticated normalisation layers on top of the raw bank APIs.
For now: GoCardless is the easiest place to start. Free tier, good sandbox, solid documentation, clear API. If you need payment initiation or richer enrichment, look at Tink. If you need global coverage, Salt Edge.