You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[META] Bank integrations: local-first bank connectivity #13052
This is an actionable part of #4640 which describes how we can start integrating banks. The goal is to make rotki an all-around financial tracking tool by adding bank account support:
current balances and, most importantly, full transaction history while staying
true to rotki's architecture: local-first, privacy-preserving, no intermediaries.
No aggregator services, no rotki servers in the data path. The app talks to the
bank directly from the user's machine, or ingests files the user downloaded
themselves.
Initial market focus: Germany (best protocol situation, and we have test
accounts), with an architecture that scales to other regions via per-bank
connectors and universal file import.
Access tiers
Every bank is classified into one of five access tiers. We always implement the
highest tier available for a given bank. The tier is recorded in the connector
manifest and shown to the user.
Tier 0 — FinTS/HBCI: the German multi-bank protocol. One implementation
covers essentially all traditional German banks (Sparkassen, VR banks,
Deutsche Bank, Commerzbank, ING, DKB, comdirect, ...). Direct client-to-bank
with the user's own credentials. Legally clean for local software (no AISP
license needed; product registration with Deutsche Kreditwirtschaft is filed,
ID pending). Provides balances (HKSAL), transactions (HKKAZ as MT940/camt),
and securities depots (HKWPD).
Tier 1 — Official customer API: documented, supported APIs banks offer
their own customers (e.g. Qonto Business API, bunq, Wise). Stable, keyed,
sometimes sandboxed.
Tier 3 — Web scraping: last resort for banks with nothing better.
Expect frequent breakage.
Tier 4 — File import: universal fallback and history-depth backfill for
every bank. 4a: standard formats (camt.052/053, MT940, OFX/QIF) parsed
deterministically. 4b: bank-specific CSV via heuristic column mapping with
one-time user confirmation, saved fingerprinted mapping templates, and a
community template pack. LLM assistance is an optional accelerator, never a
dependency.
Architecture
All tiers feed one normalization layer: a canonical even schema with incremental sync cursors, stable IDs where the source provides
them, and content-hash idempotency/dedup where it doesn't (file imports overlap
with live syncs by design — dedup is a core requirement, not an edge case).
Non-FinTS banks are implemented as connectors against a framework contract:
Manifest: bank identity, access tier, capabilities (balances /
transactions / holdings), declared secrets schema (UI renders setup forms
generically), version.
Auth primitives (shared vocabulary with the FinTS TAN UX): static_secret, username_password, otp_input, app_approval_poll
(decoupled confirmation on a paired device), challenge_display
(e.g. photoTAN image). Auth flows are declarative state machines over these
primitives; the UI implements each primitive once.
Session persistence: tokens stored encrypted locally with refresh logic,
so users don't re-approve every sync.
Error taxonomy: AuthExpired, MFARequired, RateLimited, SchemaDrift. SchemaDrift = the bank changed something; triggers the repair
process.
Testing & repair: every connector ships redacted request/response
fixtures (cassettes). Contract tests run all connectors against their
cassettes in CI. On breakage: re-record against a live account, hand the
failing diff to a coding agent, regenerate, re-run contract tests. Breakage
is routine maintenance, not an emergency.
Distribution & security: connectors are versioned packages updatable
outside the app release cycle. Secrets live in rotki's encrypted store /
OS keychain; connectors request secrets by name and never touch storage or
logs. Signed releases and mandatory review for community connectors.
Task list
Infrastructure
#___ Normalization layer: canonical bank account/transaction schema,
sync cursors, dedup/idempotency
jfyi: ZenMoney is an expense tracker app which is also facing this same issue, and they have a custom plugin system for it, as well as SMS/Push messages parsing. Some of their plugins are open-source, maybe you can reuse something from there if license permits.
I'm just an active ZenMoney user starting to use rotki, saw this issue and decided to share what I know.
Goal
This is an actionable part of #4640 which describes how we can start integrating banks. The goal is to make rotki an all-around financial tracking tool by adding bank account support:
current balances and, most importantly, full transaction history while staying
true to rotki's architecture: local-first, privacy-preserving, no intermediaries.
No aggregator services, no rotki servers in the data path. The app talks to the
bank directly from the user's machine, or ingests files the user downloaded
themselves.
Initial market focus: Germany (best protocol situation, and we have test
accounts), with an architecture that scales to other regions via per-bank
connectors and universal file import.
Access tiers
Every bank is classified into one of five access tiers. We always implement the
highest tier available for a given bank. The tier is recorded in the connector
manifest and shown to the user.
covers essentially all traditional German banks (Sparkassen, VR banks,
Deutsche Bank, Commerzbank, ING, DKB, comdirect, ...). Direct client-to-bank
with the user's own credentials. Legally clean for local software (no AISP
license needed; product registration with Deutsche Kreditwirtschaft is filed,
ID pending). Provides balances (HKSAL), transactions (HKKAZ as MT940/camt),
and securities depots (HKWPD).
their own customers (e.g. Qonto Business API, bunq, Wise). Stable, keyed,
sometimes sandboxed.
(e.g. N26). Structured but undocumented; expect occasional breakage.
Expect frequent breakage.
every bank. 4a: standard formats (camt.052/053, MT940, OFX/QIF) parsed
deterministically. 4b: bank-specific CSV via heuristic column mapping with
one-time user confirmation, saved fingerprinted mapping templates, and a
community template pack. LLM assistance is an optional accelerator, never a
dependency.
Architecture
All tiers feed one normalization layer: a canonical even schema with incremental sync cursors, stable IDs where the source provides
them, and content-hash idempotency/dedup where it doesn't (file imports overlap
with live syncs by design — dedup is a core requirement, not an edge case).
Non-FinTS banks are implemented as connectors against a framework contract:
transactions / holdings), declared secrets schema (UI renders setup forms
generically), version.
static_secret,username_password,otp_input,app_approval_poll(decoupled confirmation on a paired device),
challenge_display(e.g. photoTAN image). Auth flows are declarative state machines over these
primitives; the UI implements each primitive once.
so users don't re-approve every sync.
AuthExpired,MFARequired,RateLimited,SchemaDrift. SchemaDrift = the bank changed something; triggers the repairprocess.
fixtures (cassettes). Contract tests run all connectors against their
cassettes in CI. On breakage: re-record against a live account, hand the
failing diff to a coding agent, regenerate, re-run contract tests. Breakage
is routine maintenance, not an emergency.
outside the app release cycle. Secrets live in rotki's encrypted store /
OS keychain; connectors request secrets by name and never touch storage or
logs. Signed releases and mandatory review for community connectors.
Task list
Infrastructure
sync cursors, dedup/idempotency
error taxonomy, packaging/updates
repair process documentation
mapping templates, community template pack
(blocked on product ID for production, NOT blocked for development)
sync status
Some example Banks