Express example
A complete, runnable partner integration in one Express file — popup and redirect modes, the raw token exchange, polling, cancel handling and revocation, with no SDK.
Partnerdokumentationen publiceras på engelska.
Everything from getting started, the authorization flow and the callback assembled into one file. Unlike the callback page, which leans on the Node client library, this server uses raw fetch only — every request to open-banking.io is visible in the code, so it doubles as a reference implementation for any language.
It supports both modes: popup (the default on desktop — challenge=pin_code keeps the journey inside the popup) and redirect (the fallback when the popup is blocked, and the better choice on mobile). The opening page learns the outcome by polling /api/status, and the close page also broadcasts on a BroadcastChannel for browsers that still allow it.
#The whole server
Your page opens window.open('/connect?mode=popup', …) from a click handler (or sends mobile users to /connect?mode=redirect), then polls /api/status?state=… — keep the state from the redirect that started the flow — until it flips from pending. The flows and results maps are in-memory for the demo; use Redis with a TTL in production.
#The flow, end to end
#Forcing the language
Every screen of the flow normally follows the language the user picked or their browser prefers. To pin it per flow — a Danish user seeing Danish even in an English product — pass ui_locales on /oauth/authorize. The example above forwards ?lang= for it:
The server resolves the list to the first locale it publishes (da-DK and da both count as Danish) and threads it to every screen; unsupported values are ignored, and the forced language is never saved as the user's preference. See ui_locales in the reference.
#Connected but empty
/api/status can report connected while accounts is still []: the first sync of a freshly onboarded bank has not finished yet. Don't treat empty as failure — poll GET /api/connections with the same Bearer key and read lastSyncedAt on the connection: it is null until the first sync completes, and an ISO timestamp after that. Show "syncing…" until it flips, then list the accounts.
#Test cancel, test 401
Two failure paths ship in the checklist below; run both against staging before you call the integration done.
- Cancel before login — on the login screen, press "Back to {your name}". The flow arrives at
/callbackaserror=access_deniedwith no code. The example consumes thestate, marks the flow cancelled, and the poll reports it. - Cancel on consent — decline on the consent screen. Same
access_denied, and any code that screen was holding is burned first. Both cancels must land in the same branch of your handler. - Read after revoke — click disconnect (the
/api/disconnectcall above), then poll/api/statusagain. The account read answers401; the example reportsrevoked. Drop the stored bundle and offer to reconnect — never retry a401.
#Read next
- Tokens and keys — what the key can read, and how the relayed private key decrypts it.
- Reading data — the read endpoints behind
accounts.read.