A pure Rust implementation of the FinTS 3.0 (formerly HBCI) banking protocol for German online banking.
- Full FinTS 3.0 PinTan implementation (two-step and decoupled TAN)
- Typestate protocol layer — invalid dialog state transitions are compile-time errors
- Bank-specific workflows (DKB, generic FinTS banks)
- Built-in registry of 1000+ German banks with FinTS endpoints
- Account balance, transaction history (MT940), and securities/depot holdings
- CLI client for interactive banking from the terminal
- Mock server for integration testing
- Debug & audit tooling for protocol inspection
Add to your Cargo.toml:
[dependencies]
fints-rs = "0.1"The crate is published as fints-rs on crates.io but the library name is fints, so all imports use use fints::....
cargo install fints-rs --features cliThis installs the fints-client binary.
use fints::{dkb, Account, UserId, Pin, ProductId};
#[tokio::main]
async fn main() -> fints::Result<()> {
// Step 1: Connect and get TAN challenge
let (session, challenge) = dkb::connect(
&UserId::new("your_user_id"),
&Pin::new("your_pin"),
&ProductId::new("YOUR_PRODUCT_ID"),
None,
).await?;
println!("Please confirm in your banking app: {}", challenge.challenge);
// Step 2: After user confirms pushTAN, fetch data
let account = Account::new("DE12345678901234567890", "BYLADEM1001")?;
let result = session.fetch(&account, 365).await?;
println!("Balance: {:?}", result.balance);
println!("{} transactions", result.transactions.len());
Ok(())
}use fints::{Flow, UserId, Pin, ProductId};
#[tokio::main]
async fn main() -> fints::Result<()> {
// Step 1: Initiate — pass BLZ to auto-resolve the bank
let (mut flow, challenge) = Flow::initiate(
"12030000", // BLZ (bank code)
&UserId::new("your_user_id"),
&Pin::new("your_pin"),
&ProductId::new("YOUR_PRODUCT_ID"),
None, None, None,
).await?;
println!("TAN challenge: {}", challenge.challenge);
// Step 2: After TAN confirmation, fetch balance + transactions
let result = flow.confirm_and_fetch(
"DE12345678901234567890", // IBAN
"BYLADEM1001", // BIC
90, // days of history
).await?;
println!("Balance: {:?}", result.balance);
println!("{} transactions", result.transactions.len());
println!("{} holdings", result.holdings.len());
Ok(())
}use fints::{all_banks, bank_by_blz};
// Look up a bank by BLZ
if let Some(bank) = bank_by_blz("12030000") {
println!("{} — {}", bank.name, bank.url);
}
// List all known banks
for bank in all_banks() {
println!("{}: {} ({})", bank.blz, bank.name, bank.url);
}The typestate Dialog API enforces correct protocol usage at compile time:
use fints::protocol::Dialog;
use fints::types::*;
// Dialog<New> → sync → Dialog<Synced> → open → Dialog<Open> → business ops
// Calling business methods on Dialog<New> won't compile.States: New → Synced → Open → TanPending → back to Open after TAN.
fints-client --bank dkb setup
# or by BLZ:
fints-client --bank 12030000 setupThis performs a sync dialog, discovers accounts and TAN methods, and saves a session file for future use.
fints-client --bank dkb sync
fints-client --bank dkb sync --days 365
fints-client --bank dkb sync --all-accountsfints-client --bank dkb balance
fints-client --bank dkb balance --iban DE12345678901234567890fints-client --bank dkb transactions
fints-client --bank dkb transactions --from 2024-01-01 --to 2024-12-31
fints-client --bank dkb transactions --output json
fints-client --bank dkb transactions --output csvfints-client --bank dkb holdingsfints-client --bank custom --url https://banking.example.de/fints --blz 12345678 syncfints-client sessions list
fints-client sessions inspect dkb
fints-client sessions delete dkb# Inspect bank capabilities
fints-client --bank dkb inspect
# Decode a raw FinTS message
echo "<base64_message>" | fints-client decode --b64
# Audit a bank's protocol compliance
fints-client audit --blz 12030000All data commands support --output human (default), --output json, and --output csv.
| Variable | Description |
|---|---|
FINTS_PRODUCT_ID |
Override the default FinTS product registration ID |
FINTS_SESSION_DIR |
Override the session storage directory |
--bank <BLZ|name> Bank identifier (BLZ or shorthand like "dkb")
--user <USER_ID> FinTS user ID
--pin <PIN> PIN (prefer interactive prompt instead)
--product-id <ID> FinTS product registration ID
--verbose / -v Show decoded FinTS segments
--debug-wire Show hex wire dumps
--no-persist Don't save session; print resume token instead
--resume-token <TOK> Resume from a previously printed token
┌─────────────────────────────────────────────────┐
│ Flow API │ ← High-level 2-step TAN flow
├─────────────────────────────────────────────────┤
│ Workflow layer │ ← Bank-specific logic (DKB, Generic)
│ BankOps trait + AnyBank │
├─────────────────────────────────────────────────┤
│ Protocol layer │ ← Typestate Dialog<S> state machine
│ Dialog<New|Synced|Open|TanPending> │
├──────────────────┬──────────────────────────────┤
│ Segments │ Message builder │ ← HKSAL, HKKAZ, HKTAN, ...
├──────────────────┼──────────────────────────────┤
│ Parser │ Serializer │ ← Wire format (DEG/DE encoding)
├──────────────────┴──────────────────────────────┤
│ Transport (HTTPS) │ ← Base64 over HTTPS POST
└─────────────────────────────────────────────────┘
| Feature | Description |
|---|---|
cli |
Enables the fints-client binary (clap, rpassword, comfy-table, csv, flate2) |
server |
Enables the fints-server mock server binary (axum) |
No features are enabled by default — the library is lightweight with minimal dependencies.
To use FinTS with German banks in production, you need a registered product ID from the Deutsche Kreditwirtschaft. Set it via:
ProductId::new("YOUR_ID")in code--product-id YOUR_IDon the CLIFINTS_PRODUCT_ID=YOUR_IDenvironment variable
MIT