fints-cli docs
This is the public part of the fints-cli README. The full README in English and German, the security notes and the changelog are in the private repository you get access to with your purchase.
Installation
Requirements: Python 3.12 or newer (tested with 3.12, 3.13 and 3.14) and uv or pipx. Tested on Linux. No compiler needed.
fints-cli is sold per purchase, and the repository is private. After buying you
get an invitation to github.com/sofatools/fints-cli - accept it (GitHub sends
it by e-mail and shows it on github.com). There is no public PyPI package.
1. Give git access to the private repository - one of:
- GitHub CLI (simplest):
gh auth login, thengh auth setup-git. From then on git - and with it uv and pipx - uses your GitHub login forhttps://github.com/.... - An SSH key on your GitHub account: use the
git+ssh://URL below.
2. Install the fints command:
uv tool install git+https://github.com/sofatools/fints-cli
fints --version
Alternatives:
uv tool install git+https://github.com/sofatools/fints-cli@0.1.0 # a fixed release
uv tool install git+ssh://git@github.com/sofatools/fints-cli # via SSH
pipx install git+https://github.com/sofatools/fints-cli # with pipx
If your shell does not find fints afterwards, run uv tool update-shell
(or pipx ensurepath) and open a new terminal.
Updating: uv tool upgrade fints-cli (pipx: pipx reinstall fints-cli). See
CHANGELOG.md for what changed.
From a clone, for development: git clone https://github.com/sofatools/fints-cli,
then uv sync && uv run pytest, or uv tool install . to install that checkout.
Product ID (required)
FinTS requires every client program to identify itself with a product ID. The ID is tied to the person or company who registered it, so it is not shipped with this tool - you register your own. The registration with the Deutsche Kreditwirtschaft is free:
- Open https://www.fints.org/de/hersteller/produktregistrierung and fill in the registration form (product name, your name and address, a short description such as “private read-only account access”).
- Send the form by e-mail to registrierung@hbci-zka.de.
- After about 10-15 working days you receive your registration number. In FinTS terms this is the “Produktbezeichnung” (product identifier) that the client sends to the bank.
- Export it in the environment where you run
fints:
export FINTS_PRODUCT_ID="0123456789ABCDEF012345678" # 25 characters
Without FINTS_PRODUCT_ID the tool stops with a message pointing here.
Configuration
~/.config/fints-cli/config.toml ($XDG_CONFIG_HOME is honoured; --config
or FINTS_CONFIG override the path). One table per bank:
[banks.gls]
blz = "43060967"
url = "https://fints1.atruvia.de/cgi-bin/hbciservlet"
login = "1234567890"
pin_env = "FINTS_PIN_GLS"
# or: pin_command = "pass show banking/gls/pin"
tan = "auto" # auto | decoupled | manual
# tan_mechanism = "946" # optional: security function id, see `fints login`
blz,url,login: bank code, FinTS server URL (must start withhttps://) and your online banking login (often the account number or an alias). Unknown keys are reported, so a typo does not go unnoticed.- PIN: exactly one of
pin_env(name of an environment variable holding the PIN) orpin_command(a shell command whose first line of output is the PIN, e.g.pass show banking/gls/pin). The PIN is never written to disk, never logged and never part of an error message. tan:auto(default): decoupled approval in the app when the bank offers it, otherwise a prompt for a TAN to type;decoupled: only accept app approval, fail if the bank wants a typed TAN;manual: do not poll; you press Enter after confirming in the app (needs a terminal; if the app has not confirmed yet, you are asked again).
tan_mechanism: force a security function id.fints loginprints the ids and names the bank offers.
With several banks in the file, pass --bank <name> to every command
(fints balance --bank gls or fints --bank gls balance). The bank
name becomes a file name in the session cache, so stick to letters, digits,
- and _.
Usage
fints accounts # IBAN, BIC, account number, bank
fints accounts --format json
fints balance # all accounts
fints balance DE00430609670000009600 --format json
fints transactions # last 30 days, CSV
fints transactions --since 2026-01-01 --until 2026-03-31 --format json
fints transactions DE00430609670000009600 --since 2026-07-01 --format ledger
fints transactions --format beancount --account "Assets:Bank:{bank}"
fints login # force a fresh approval, list TAN mechanisms
fints mcp # MCP server on stdio
CSV columns: booking_date, value_date, amount, currency, counterparty, counterparty_iban, purpose, reference, iban, posting_text. JSON has the same
fields; amounts are strings ("-514.22") so no precision is lost.
Dates are YYYY-MM-DD. --account is a template for ledger/beancount output;
{iban} and {bank} are replaced ({bank} upper-cased, e.g. Assets:Bank:GLS,
because Beancount account names must start with a capital letter). Unknown counter accounts are
Expenses:Unknown / Income:Unknown for you to edit.
Exit codes: 0 ok, 1 bank or network error, 2 usage or configuration
error, 3 approval needed (confirm in the app and run the command again),
130 interrupted (Ctrl-C).
When the bank asks for an approval, the CLI prints Waiting for approval in your banking app: ... on stderr and polls for up to five minutes.
Data goes to stdout, messages to stderr, so fints transactions --format json > july.json gives a clean file. -v / --verbose (before or after the
command: fints -v balance) also shows the bank’s response notes (Dialog response: 3060 - ...) and, for unexpected errors, the traceback. They never include the
PIN or a TAN.
A bank that does not answer is given up after 20 s (connecting) or 120 s (answer), instead of hanging.
MCP server
fints mcp serves three tools over stdio, all marked read-only
(readOnlyHint): list_accounts, get_balance(iban?),
get_transactions(since, until?, iban?). Each takes an optional bank.
Claude Code:
claude mcp add fints -e FINTS_PRODUCT_ID=your-registration-number -- "$(which fints)" mcp
(Add -s user to have it in every project, not just the current one.)
Claude Desktop (claude_desktop_config.json) and other MCP clients - use the
absolute path of the fints binary, GUI apps do not see your shell’s PATH;
which fints shows it:
{
"mcpServers": {
"fints": {
"command": "/home/you/.local/bin/fints",
"args": ["mcp"],
"env": {
"FINTS_PRODUCT_ID": "your registration number"
}
}
}
}
For the PIN, use pin_command in config.toml (for example
pin_command = "pass show banking/gls/pin") so that the password manager hands
it over at runtime. Do not put the PIN into the env block: MCP client
configuration files are plain text, often shared between machines or committed
to a repository. If you must use pin_env, set the variable in the environment
that starts the MCP client, not in its config file.
Use "args": ["--config", "/path/to/config.toml", "mcp"] for a config file
elsewhere, and --bank to pre-select a bank. tan = "manual" cannot be
driven through MCP (nobody presses Enter); use auto for banks served this way.
When the bank wants an approval, the tool does not block. It answers at once with
{"status": "approval_needed", "message": "Approval needed: confirm the request in your banking app, then call this tool again."}
while the bank request keeps waiting in the background (up to five minutes).
Confirm in the app, call the tool again, and you get the result. If the
approval timed out, the next call starts a new one. A second call for the same
bank while the first is still running (but not waiting for an approval) gets
{"status": "busy", ...}: call again a few seconds later. There is never more
than one bank dialog - and so never more than one push approval - per bank at
a time.
Errors (wrong PIN, bank not reachable, unknown IBAN, configuration problems) come back as a tool error with a one-line message. If the bank rejects the PIN, the server does not send that PIN again until it is restarted or the PIN changes - an agent retrying the call cannot lock your online banking access.
Giving an AI agent access to your bank data is your decision; the tools cannot move money, but they do expose balances and transactions to whatever reads the MCP output.
The 90-day rule
Under PSD2, banks require a fresh strong authentication at least every 90 days
for account access. In practice: every now and then a call will ask for an
approval again even though the session cache is warm. Confirm in the app and
run the command again. fints login forces this deliberately - it deletes the
cached session and opens a fresh dialog.
The session cache lives in ~/.local/state/fints-cli/<bank>.session
($XDG_STATE_HOME is honoured). It holds the bank’s parameter data and your
account list - not the PIN - and is written with mode 0600. Deleting the file
is always safe.
Troubleshooting
| Message | What to do |
|---|---|
FINTS_PRODUCT_ID is not set |
Export your product ID, see above. |
environment variable FINTS_PIN_GLS ... is not set |
Export it, or use pin_command. |
bank 'gls' rejected the login: wrong PIN or login ... |
Check login and the PIN. Do not retry blindly - banks lock the access after a few wrong attempts. |
cannot reach bank ... host name not found / connection refused / no answer within ... |
Network down, or a typo in url. |
Approval needed: not confirmed within 300 s |
Run the command again and confirm in the app. |
the bank asks for a TAN to type, but this bank is configured with tan = "decoupled" |
Set tan = "auto". |
anything with unexpected |
Run again with -v and open an issue with the output (it contains no PIN). |
Source: fints-cli README.md, release 0.1.0, commit 117fd72