WealthMedia

Building a Personal Finance App with Open Banking APIs in 2026 (No Certificates Required)

Building a personal finance app used to mean begging users to paste a CSV export — or worse, to share their banking password with a screen-scraping service. Open Banking was supposed to fix that, and under. . .

· 8 min read

Building a Personal Finance App with Open Banking APIs in 2026 (No Certificates Required)

Building a personal finance app used to mean begging users to paste a CSV export — or worse, to share their banking password with a screen-scraping service. Open Banking was supposed to fix that, and under PSD2 it largely has. Every regulated EU/UK bank now exposes a standard API that returns your balances and transactions as clean JSON, with your explicit consent. So why isn't every indie developer shipping a net-worth dashboard on top of it?Because the "official" path to those APIs requires aneIDAS qualified certificate(QWAC + QSeal), a regulator licence, and weeks of compliance paperwork. That's the wall most tutorials quietly skip. This one doesn't. We'll cover how PSD2 APIs actually work, why the certificate requirement blocks indie builders, and then build a working personal-finance tracker end-to-end using a certificate-free provider — with real curl and Python you can run today. What an "Open Banking API" actually meansTwo pieces of jargon do most of the heavy lifting, and clearing them up front will save you hours:PSD2is the EU directive (second Payment Services Directive) that forced banks to open up customer data to licensed third parties, on the customer's request. The UK'sOpen Bankingstandard is the local implementation of the same idea. AISP / PIS— anAccount Information Servicereads data (balances, transactions); aPayment Initiation Servicecan move money. For a personal finance app you only ever needAIS. That's read-only by law. The thing that makes Open Banking safe — and the thing that makes it annoying to set up — is theconsent redirect flow. Your app never sees the user's bank password. Instead:Your app asks the API provider to start a connection for, say, "Nordea" or "Lunar". The provider redirects the user to theirown bank'slogin page (the bank's domain, the bank's TLS). The user logs in there and approves a scoped consent (e. g. "read balances and transactions for 90 days"). The bank redirects back to your app with an authorisation code. Your app exchanges that code for access/refresh tokens and can now poll the data API. At no point do you touch credentials. The bank hands you a token with a defined scope and an expiry. The certificate wall (and why it blocks indie builders)Here's the part the vendor docs bury. To call a bank's PSD2 APIdirectly, as your own licensed third-party provider (TPP), you must present aQWAC(Qualified Website Authentication Certificate) and aQSeal(Qualified Electronic Seal) — collectively theeIDAS qualified certificates. You get those by:Becoming an authorised AISP with your national regulator (the FCA in the UK, Finanstilsynet in Denmark, BaFin in Germany, etc. ). Passing audits and maintaining a compliance programme. Buying the certificates from a qualified trust service provider — typically€1,000–€6,000/year, plus setup. For a bank or a funded fintech, that's Tuesday. For an indie developer who just wants to build a net-worth dashboard over the weekend, it's a non-starter. So either you give up, or you use anaggregatorthat is already licensed and lets you ride on its authorisation — handing you a simple API key instead of a certificate. That second option is the pragmatic one, and it's what the rest of this tutorial uses. The certificate-free approachA handful of providers are already licensed AISPs and expose their aggregated API to you behind a plainAPI key. They hold the certificates; you hold a key. You still go through the bank's consent redirect — the security model is identical — but you skip the licensing and certificate procurement entirely. The provider used in the code below isopen-banking. io(full disclosure: it's a service I built, which is exactly why I can show you the cheapest path). It's certificate-free, costs €3/month for one account, and connects to EU/UK banks. The pattern — API key, consent flow, JSON data — is the same one you'll see at Yapily, Tink, TrueLayer, or Enable Banking; the difference is the absence of certificates and the price tag. Adapt the code to whichever provider you pick. Now let's build something. Step 0 — Get API access (no certificates, ~2 minutes)Create an account and connect a real bank through the consent redirect described above. Generate anAPI keyand export acredentials. jsonbundle. The bundle pairs your API key with your private decryption key (more on why in the security section). There is no eIDAS step. There is no audit. You authenticate every request with a single header:curl-H"X-Api-Key:$OBIO_API_KEY"https://open-banking. io/api/accountsEnter fullscreen modeExit fullscreen modeThat's the entire auth model. No OAuth client-secret dance on your side, no mTLS, no certificate rotation. Step 1 — List accounts and balances (Python)Install the Python client (or just usehttpxagainst the raw REST endpoint — your call):pipinstallopen-banking-ioEnter fullscreen modeExit fullscreen modeThen list every connected account with its booked balance:fromopen_banking_ioimportOpenBankingClientwithOpenBankingClient. from_credentials("credentials. json")asclient:foraccountinclient. get_accounts():booked=next((bforbinaccount. balancesifb. type=="ITBD"),None)label=account. display_nameoraccount. owner_nameprint(f"{label}({account. iban}):{booked. amount}{account. currency}")Enter fullscreen modeExit fullscreen modeRun it and you get something like:Drift (DK64 6466…4927): 12482. 55 DKK Salary (DK21 3000…8841): 828. 13 DKK Buffer (DK90 5301…2207): 34010. 00 DKKEnter fullscreen modeExit fullscreen modeTwo things worth noting. First, balances come in types (ITBD= interim booked,CLBD= closing booked,XPCD= expected, etc. ) — always pick the one that matches your use case. "Booked" is the safe default for a net-worth figure because it excludes pending authorisations. Second,amountis adecimal. Decimal, not a float — keep it that way or you'll silently lose pennies to floating-point error. This trips up a lot of finance apps. Step 2 — Pull and paginate transactionsdefload_txns(client,account_id,page_size=200):txns,offset=[],0whileTrue:page=client. get_transactions(account_id,limit=page_size,offset=offset)txns. extend(page. items)iflen(page. items)0:income+=n["amount"]else:spend+=-n["amount"]print(f"Income:{income:>12,. 2f}")print(f"Spending:{spend:>12,. 2f}")print(f"Net cashflow:{(income-spend):>12,. 2f}\n")print("Top 5 spenders:")forname,amtinsorted(by_counterparty. items(),key=lambdakv:kv[1])[:5]:print(f"{name:30s}{amt:>10,. 2f}")Enter fullscreen modeExit fullscreen modeThat's a real, working spend tracker. From here the usual next steps are obvious: bucket by month, flag recurring subscriptions (same counterparty, same amount, ~30-day cadence), raise an alert when income drops, and project a 30-day balance forecast. None of that needs the bank API at all — you already have clean, signed, normalised data. Step 4 — Schedule a daily sync and cacheYou don't want to hit the bank on every page load. The standard pattern is a daily server-side job that pulls fresh transactions into your own database; your frontend reads from your DB, not the bank. # cron / scheduled job — run once per daywithOpenBankingClient. from_credentials("credentials. json")asclient:client. sync_all()# refresh every connected account from the bankforacctinclient. get_accounts():fortinload_txns(client,acct. id):upsert_into_db(t)# idempotent insert keyed on the transaction idEnter fullscreen modeExit fullscreen modesync_all()triggers an online refresh;get_transactions()then returns the latest rows. Keep the credentials bundle server-side only (see below) and you have a textbook ETL: bank → provider → your DB → your app. Security: zero-knowledge, and why it matters for a finance appA personal finance app is a high-trust product. Two security properties are worth understanding before you ship:The consent layer is bank-enforced. Read-only is not a setting in your code — it's a legal scope the bank grants at consent time. Your token simply cannot initiate a payment if you only asked for AIS. That's a much stronger guarantee than a "we promise not to move money" checkbox. End-to-end encryption / zero-knowledge. The better providers don't store your data in a form they can read. open-banking. io, for example, encrypts every account and transaction envelope under a key derived fromyourprivate key (ECDH P‑256 → HKDF‑SHA256 → AES‑256‑GCM). The server holds ciphertext it cannot decrypt; decryption happens in your client process, using the private key from yourcredentials. json. If the provider is breached, the attacker gets sealed blobs. Practical implication:thecredentials. jsonbundle contains your private decryption key. Never ship it to a browser, a mobile app, or a public repo. Keep it on your server — ideally as a Kubernetes Secret or a secrets manager, mounted read-only:kubectl create secret generic obio-credentials\--from-file=credentials. json=. /open-banking. io-credentials-2026-08-12. jsonEnter fullscreen modeExit fullscreen modeA word on costMost aggregators price for enterprise: per-connection fees in the euros, minimum commits, sales calls. That's fine if you're a funded startup, brutal if you're an indie. The certificate-free providers sit at the cheap end of the spectrum — open-banking. io is €3/month for one account and €1/month per additional account, no minimum. When you pick a provider, model your cost per active user, because transaction volume and connection count are where the bill climbs. What you've gotAt this point you have:A way to read bank datawithout an eIDAS certificate or a licence. A consent flow that's bank-enforced and read-only by law. Clean, normalised transactions to build any insight on top of. A daily-sync ETL pattern with zero-knowledge encryption. That's the complete foundation of a personal finance app — net worth, cashflow, subscription tracking, the lot — built on PSD2 APIs without touching the certificate wall. The hard part of fintech isn't the data plumbing any more; it's the product you build on top of clean data. Go build something worth using. Disclosure: I'm the developer behind open-banking. io, which is why I can point you at the cheapest, certificate-free path. The architecture in this post works with any PSD2 aggregator — swap the SDK and API key for Yapily, Tink, TrueLayer, or Enable Banking and the same steps hold.

Source: dev.to

More Banking news