If you've ever tried to call a European bank's open banking endpoint directly — say, to read your own account balances programmatically — you probably hit a wall that looks something like this:
HTTP 401 Unauthorized
{"error": "invalid_client", "error_description": "mutual TLS certificate required"}
That's the eIDAS certificate gate. Under PSD2, a bank's production API is protected by mutual TLS using a qualified certificate (a QWAC), and request signing often needs a second one (QSeal). For a regulated bank or a well-funded fintech, that's a tax you pay. For an indie developer, a student project, or a small business automating its own accounting, it's a deal-breaker: the certs cost roughly EUR 2,000–10,000 per year, take weeks of paperwork, and require a registered legal entity.
The good news: there's a well-defined, fully compliant path to read EU bank data without ever touching an eIDAS certificate yourself. This guide explains how it actually works under the hood, when to use it, and includes real, runnable code.
Disclosure up front: I'm John, the founder of open-banking.io — one of the providers in this space. I'll use our API for the worked code example because I know it best, but the architecture I'm describing is how every certificate-free aggregator works. I'll be honest about where direct access is the better choice.
The two paths to a bank's API
Every PSD2 Account Information Service (AIS) call ultimately lands on the same bank endpoint. The question is who holds the certificate.
Path A — Direct (you hold the eIDAS certificate)
You apply for an eIDAS QWAC and QSeal from a qualified trust service provider, register as a TPP (Third Party Provider) with each national regulator, onboard with each bank's developer portal, and call the bank directly over mutual TLS.
- Cost: EUR 2k–10k/year per certificate + legal/regulatory overhead
- Time to first call: 4–12 weeks
- Coverage: one bank cluster at a time (you onboard bank-by-bank)
- Best for: regulated fintechs with high volume, enterprise treasuries, anyone who needs to avoid a data intermediary in the path
Path B — Aggregator (the aggregator holds the certificate)
You sign up with an AIS aggregator, get an API key, and call their unified API. They hold the eIDAS certificates, maintain the bank integrations, handle the per-bank quirks (Berlin Group vs STET vs UK Open Banking vs Polish API standards), and present you with one consistent REST interface.
- Cost: free tiers exist (small projects), usage-based pricing at scale
- Time to first call: minutes
- Coverage: hundreds/thousands of banks across the EU/EEA through one integration
- Best for: indie devs, SMBs, PFM apps, self-hosters, anyone who values shipping over regulatory plumbing
| Dimension | Direct (eIDAS) | Aggregator (cert-free) |
|---|---|---|
| Certificate required? | QWAC + QSeal (you buy) | None |
| Setup time | Weeks–months | Minutes |
| Upfront cost | EUR 2k–10k/yr | $0 on free tiers |
| Bank coverage | Per-bank onboarding | 1,000s of banks, one API |
| Data path | You <-> Bank | You <-> Aggregator <-> Bank |
| GDPR control | Full (no intermediary) | Depends on provider (look for EU data residency + E2E encryption) |
| Per-bank quirks | You handle them all | Abstracted away |
The GDPR row is the one people underestimate. A good aggregator gives you EU-only data residency and ideally end-to-end encryption where only you hold the decryption key — meaning the aggregator genuinely can't read your transactions in the clear. That's the property you're trading the direct path for, so verify it before you sign up.
How the cert-free flow actually works
Aggregators don't "skip" PSD2 — they comply with it on your behalf. Here's the sequence, and it's the same whether you use Nordigen/GoCardless, Tink, TrueLayer, Enable Banking, or open-banking.io:
1. Create a consent / requisition -> you POST "user wants bank X"
2. Redirect user to bank (SCA) -> user authenticates at their bank
3. Bank calls back to aggregator -> consent is now "valid"
4. You list accounts -> GET /accounts
5. You fetch balances & transactions -> GET /accounts/{id}/transactions
6. Consent expires (90 / 180 days) -> repeat from step 1
The eIDAS certificate is used in step 2 and 3 — when the aggregator talks to the bank's production endpoints. Your API key only authenticates you to the aggregator, over standard HTTPS. You never see mTLS.
The two things that bite people:
- SCA (Strong Customer Authentication): the user must actively authenticate at their bank — a redirect, an app-to-app handoff, or a decoupled push. You can't silently scrape. Plan your UX around a browser/redirect hop.
- Consent expiry: PSD2 limits AIS consent. Common values are 90 days (Germany) or 180 days (many other markets), and users can revoke at any time. Your code must handle re-consent gracefully — store the consent expiry timestamp alongside the consent id and trigger re-auth before it lapses.
Real code: your first cert-free bank-data call
Here's the actual pattern, using open-banking.io's API as the worked example. The shape (create requisition -> redirect -> list -> fetch) is identical across providers; only the field names change.
Step 1 — Create a requisition (pick a bank)
# Replace YOUR_API_KEY with the key from your provider's dashboard
curl -X POST https://api.open-banking.io/v1/requisitions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"redirect": "https://yourapp.example.com/callback",
"institution_id": "DANMARKS_DANSKE_BANK",
"reference": "user-42-first-link"
}'
{
"id": "req_8f3a...",
"link": "https://api.open-banking.io/redirect/req_8f3a...",
"accounts": [],
"status": "CR"
}
Send the user's browser to link. They authenticate at Danske Bank (SCA). On success, the bank redirects back to your redirect URL.
Step 2 — List the linked accounts
curl https://api.open-banking.io/v1/requisitions/req_8f3a... \
-H "Authorization: Bearer YOUR_API_KEY"
{
"id": "req_8f3a...",
"status": "LN",
"accounts": ["acc_a1b2...", "acc_c3d4..."]
}
status: "LN" means linked. Now grab data.
Step 3 — Fetch balances and transactions
# Balances
curl https://api.open-banking.io/v1/accounts/acc_a1b2.../balances \
-H "Authorization: Bearer YOUR_API_KEY"
# Transactions (last 30 days)
curl "https://api.open-banking.io/v1/accounts/acc_a1b2.../transactions?date_from=2026-06-21&date_to=2026-07-21" \
-H "Authorization: Bearer YOUR_API_KEY"
A tiny Python client
import os, requests
API = "https://api.open-banking.io/v1"
KEY = os.environ["OBI_API_KEY"]
H = {"Authorization": f"Bearer {KEY}"}
def create_requisition(institution_id, redirect_url):
r = requests.post(f"{API}/requisitions", headers=H, json={
"redirect": redirect_url,
"institution_id": institution_id,
})
r.raise_for_status()
return r.json()
def list_accounts(requisition_id):
r = requests.get(f"{API}/requisitions/{requisition_id}", headers=H)
return r.json()["accounts"]
def get_transactions(account_id, date_from, date_to):
r = requests.get(
f"{API}/accounts/{account_id}/transactions",
headers=H, params={"date_from": date_from, "date_to": date_to},
)
return r.json()["transactions"]
# Usage
req = create_requisition("DANMARKS_DANSKE_BANK", "https://yourapp.example.com/cb")
print("Send user to:", req["link"])
# ... after the user authenticates at their bank ...
for acct in list_accounts(req["id"]):
txns = get_transactions(acct, "2026-06-21", "2026-07-21")
print(acct, len(txns), "transactions")
Check each provider's live docs for the exact field names — they vary. The flow above is universal across PSD2 AIS.
Five gotchas that cost me real time
- Consent expiry is per-country, not universal. Germany defaults to 90 days; the Nordics often allow 180. Store the expiry per consent and notify users at T-7, T-3, and T-0. A silent expiry is the #1 reason "my sync stopped working" tickets.
- Bank coverage has real gaps. "Supports 10,000 banks" hides the fact that your specific regional Sparkasse or niche neobank may be missing or sandbox-only. Test your actual target banks before committing.
-
Transaction booking vs pending. Most APIs expose both
bookedandpendingtransactions. Dedupe bytransactionId(orentryReference); if a bank doesn't supply one, hash(date, amount, counterparty, description)and treat collisions carefully. - Rate limits and polling. Refreshing transactions every minute will get you throttled — and PSD2 limits how often you can pull anyway. Once or twice a day per account is usually plenty; some banks also support webhooks for new transactions.
- GDPR / data residency. If your users are EU-based and you're shipping a commercial product, confirm the aggregator processes data in the EU and offers end-to-end encryption. "Compliant with PSD2" is not the same as "we can't read your data."
When should you actually get the certificate?
The aggregator path isn't always right. Go direct with your own eIDAS certificate when:
- You're a regulated AIS/PISP and volume justifies it (rough rule of thumb: once you're paying the aggregator more than ~EUR 2k/year, the math flips).
- You're an enterprise treasury or bank-adjacent product where no third party may sit in the data path (regulatory or policy reasons).
- You need Payment Initiation (PIS) at high reliability and the aggregator's PIS coverage is thin for your target banks.
For everyone else — hobby projects, SMB accounting automation, personal finance dashboards, self-hosted budgeting tools (Actual Budget, Firefly III, Beancount integrations) — the certificate-free aggregator path is almost always the pragmatic choice. You get to ship this week instead of next quarter.
TL;DR
- You don't need an eIDAS certificate to read EU bank data. An AIS aggregator holds it for you; you get an API key.
- The flow is universal: create requisition -> SCA redirect -> list accounts -> fetch transactions -> renew consent before it expires.
- Pick a provider on three axes: bank coverage for your banks, EU data residency + E2E encryption, and a free tier that fits your stage.
- Go direct (buy the cert) only when volume, regulation, or a no-intermediary requirement demands it.
If you want to try the flow above end-to-end, you can grab a free API key at open-banking.io — and yes, that's my project, so apply appropriate skepticism and compare it against Tink, TrueLayer, GoCardless (formerly Nordigen), and Enable Banking before you commit. The right answer depends on your banks and your budget, not on who wrote this article.
Questions or war stories from your own bank-data integrations? Drop them in the comments — I read every one.
Top comments (0)