DEV Community

John Frandsen
John Frandsen

Posted on

Migrating Off Nordigen: A Field Guide for Indie Builders (2026)

What happened

The short version: the era of free bank data for side projects is over at the big aggregators.

Nordigen — the Latvian open-banking startup that ran a genuinely free account-information API — was acquired by GoCardless in 2022 and is being folded into "GoCardless Bank Account Data". The free tier has been sunset. And GoCardless Bank Account Data itself stopped taking new onboarding back in 2025. As of this writing, there's no indication that access is coming back for new independent developers.

If you built on Nordigen, you already know all this. If you were about to, this article saves you an afternoon of dead-end signup pages.

Who's actually affected:

  • Budgeting apps pulling balances and transactions nightly
  • Expense trackers categorizing card spending
  • Spreadsheet users pushing transactions into Google Sheets
  • Indie SaaS with a "connect your bank" button that a few hundred real people use

All of these ran fine on a free tier. None of them have a procurement department.

Your realistic options

Three paths, roughly ordered by pain.

1. Direct PSD2 with your own eIDAS certificates

You register as an account information service provider, get through the paperwork, and obtain QWAC and QSealC certificates from a qualified trust service provider. Then you integrate with each bank's API yourself.

Real costs, as of 2026:

  • QWAC + QSealC certificates: €2,000–€10,000 per year, depending on the trust service provider
  • Months of paperwork — audit evidence, policy documents, insurance, registration processes
  • Per-bank overhead — some banks require their own onboarding and certificates on top
  • Permanent maintenance — you own every bank's API quirks and breaking changes forever

This makes sense if bank connectivity IS your product. It does not make sense for a budgeting app with 40 users.

2. Aggregator with your own bank-issued keys (the cert-free middle path)

Some aggregators handle the licensing and certificate burden themselves and let you bring your own bank-issued client credentials. You register with each bank yourself (usually a web form, often same-day approval), drop the client ID/secret into the aggregator's API, and get one harmonized REST interface across banks. The aggregator does the certificate dance; you never see a QWAC.

You keep the keys, which means you can always go direct later. This is where most ex-Nordigen indies land.

3. Full broker — the aggregator holds everything

The classic enterprise model: the aggregator holds the license, the certificates, and the bank relationships. You never touch a bank credential. In exchange you get sales calls, per-call pricing tiers, and contract cycles. Fine for fintechs; heavy for side projects.

Migration mechanics

Consents do not travel

A PSD2 consent is best understood as OAuth-for-bank-data: the bank issues it to a specific account-information provider, for a specific user, for a limited window. When you switch providers, the consent stays behind. There is no token export. Every end-user re-authorizes through the new provider's flow.

Practical consequences:

  • Every user re-authenticates once. Plan your comms; expect a dip in connected accounts.
  • The 90-day clock resets. Recurring-access consents under PSD2 last up to 90 days (180 in some jurisdictions as rules evolve — plan for 90). After migration, day one is day one again.
  • Nordigen "requisitions" and "end-user agreements" can't be moved or converted. They die with the account.

Historical data: export before you cancel

Most banks serve around 90 days of transaction history per consent window through the API. Some serve more; few serve years. If your old provider cached more history than that, export it before you deactivate the account — raw accounts, balances, transactions, metadata — into storage you control. Don't count on re-fetching two years of history through the new provider; the bank may simply not serve it.

Dedupe: the stable transaction ID

Most banks give transactions a stable ID. Use it as your dedupe key — but hash it together with the booking date:

import hashlib

def dedupe_key(txn):
    raw = f"{txn['transaction_id']}|{txn.get('booking_date', '')}"
    return hashlib.sha256(raw.encode()).hexdigest()
Enter fullscreen mode Exit fullscreen mode

Why hash ID + booking date? Because some banks re-issue the same ID for a corrected transaction with a different date, and you want that to land as a new row, not get swallowed.

Then watch booked vs pending. The same logical transaction typically shows up first as pending and later as booked. Some banks give both entries the same ID; some don't. Two rules that save you:

  • Ingest pending and booked into a state column (or separate paths), and only count booked rows in financial reports.
  • Expire pendings older than N days instead of promoting them — card authorizations that get dropped never become booked.

The classic migration bug: re-fetching overlapping windows after the switch, storing pending and booked as separate rows with auto-generated keys, and double-counting a month of groceries.

FITID mapping (QuickBooks / OFX users)

If you push transactions into a .qbo/OFX file or feed, the <FITID> element is the dedupe key on the receiving side. Map it deterministically:

  • Prefer the bank's transaction ID, sanitized (OFX hates angle brackets and control characters; mind length limits).
  • If a bank truly gives no usable ID, hash (amount, booking_date, remittance_info) — but identical coffee purchases on the same day will collide, so add a sequence salt.
  • Never generate FITIDs from a timestamp. QuickBooks will happily import the same transaction every time, forever.

Endpoint mapping

Nordigen's API shape maps almost 1:1 onto a generic PSD2 AIS shape. The renaming: requisition → consent, institution → bank, end-user agreement → consent terms.

import requests

# Nordigen                          ->  Generic PSD2 AIS
# POST /token/new/                      POST /oauth/token
# GET  /institutions/                   GET  /banks
# POST /requisitions/                   POST /consents        (returns an auth URL)
# GET  /requisitions/{id}               GET  /consents/{id}
# GET  /accounts/{id}/balances/         GET  /accounts/{id}/balances
# GET  /accounts/{id}/transactions/     GET  /accounts/{id}/transactions
#      ?date_from=2026-01-01                 ?date_from=2026-01-01&bookingStatus=both

API = "https://api.your-new-provider.example"

tok = requests.post(
    f"{API}/oauth/token",
    auth=(CLIENT_ID, CLIENT_SECRET),
    data={"grant_type": "client_credentials"},
).json()["access_token"]
H = {"Authorization": f"Bearer {tok}"}

# 1. create a consent (was: create a requisition)
consent = requests.post(
    f"{API}/consents", headers=H,
    json={"bank_id": "SANDBOX_TERTIARY_BANK", "redirect_url": REDIRECT_URL},
).json()

# 2. redirect the user to consent["auth_url"], handle the callback
# 3. list accounts and pull transactions with an overlap window

accounts = requests.get(
    f"{API}/accounts", headers=H, params={"consent_id": consent["consent_id"]}
).json()

txns = requests.get(
    f"{API}/accounts/{accounts[0]['id']}/transactions", headers=H,
    params={"date_from": "2026-07-01", "bookingStatus": "both"},
).json()
Enter fullscreen mode Exit fullscreen mode

Pull with an overlapping date window from your last successful sync — your dedupe keys absorb the overlap. For a simple app, this is one to two days of work, minus the gotchas above.

Migration checklist

  • [ ] Export all cached history from the old provider — before cancelling anything
  • [ ] Pick a path: own eIDAS certs / own-keys aggregator / full broker
  • [ ] Re-wire the auth flow; test consent creation + callback in sandbox first
  • [ ] Add (transaction_id, booking_date) dedupe with a booked/pending state column
  • [ ] Add FITID mapping if anything downstream speaks OFX
  • [ ] Migrate users in waves; each re-authorizes exactly once
  • [ ] Calendar reminder at day ~85: your first 90-day re-consent wave
  • [ ] Then cancel the old account

One disclosure, so you know who's writing this: I build open-banking.io, a certificate-free EU/UK PSD2 account-information API where you use your own bank-issued client keys (the aggregator-style license is handled for you) for about €3/month — one of the option-2 paths above, and new customers are welcome. Whatever you pick, the mechanics in this guide apply the same way.

Top comments (0)