A client library for communicating with FinTS servers.
Note: This is a fork and continuation of the excellent work by Frederick Gnodtke (Prior99). We are grateful for the solid foundation and comprehensive implementation provided by the original project.
Experimental β use with caution in production
This library includes an implementation of the FinTS 4.1 XML-based protocol (FinTS4Client). This support is experimental and subject to the following limitations:
- FinTS 4.0 is not a widely deployed version. Most German retail banks still use FinTS 3.0. FinTS 4.1 support is intended for banks and aggregators that have explicitly adopted the XML-based successor format.
- Protocol negotiation is best-effort. The implementation will automatically try fallback versions (
4.1 β 4.0 β 3.0) when a bank rejects the preferred version, but real-world servers may behave unpredictably. - TAN flows are partially supported. The interactive two-step TAN flow (PIN+TAN, chipTAN, pushTAN) is implemented via a
tanCallback, but edge cases β such as HHD/Flickercode visualisation, multi-challenge flows, or bank-specific challenge formats β may require additional handling. - BPD/UPD parsing is partially generic. The FinTS 4.1 XML structure leaves room for bank-specific element naming. The parser applies fallback strategies, but untested banks may require further mapping.
- No write operations.
FinTS4Clientis currently read-only (accounts, balances, statements). Credit transfers and direct debits are only available via the stablePinTanClient(FinTS 3.0). - TLS certificate requirements. Banks with private CA certificates or strict TLS policies require a custom Node.js
https.Agent, which must be passed viafetchOptions. See Custom TLS / HTTPS configuration below.
For production use with the widest bank compatibility, use the PinTanClient (FinTS 3.0) described in the Quick Start section.
- β Updated Dependencies: All dependencies updated to their latest stable versions
- β Modern TypeScript: TypeScript 5.x with improved type safety
- β GitHub Actions: Automated CI/CD pipeline
- β FinTS 4.1 (Experimental): XML-based protocol with interactive TAN, version negotiation, and improved BPD parsing
- β Active Maintenance: Regular updates and dependency maintenance
- β
Published as
fints-libandfints-lib-clion npm
npm install fints-lib
# or
yarn add fints-libFor the CLI tool:
npm install -g fints-lib-cli
# or
yarn global add fints-lib-cli# Install dependencies
yarn install
# Build all packages
yarn build
# Run tests
yarn test
# Run linting
yarn lintimport { PinTanClient } from "fints-lib";
const client = new PinTanClient({
url: "https://banking.example.com/fints", // Your bank's FinTS URL
name: "username",
pin: "12345",
blz: "12345678",
});
const accounts = await client.accounts();
console.log(accounts);import { FinTS4Client } from "fints-lib";
// β οΈ Experimental: most banks still use FinTS 3.0
const client = new FinTS4Client({
url: "https://banking.example.com/fints41",
name: "username",
pin: "12345",
blz: "12345678",
});
const accounts = await client.accounts();
const balance = await client.balance(accounts[0]);
const stmts = await client.camtStatements(accounts[0]);npm install -g fints-lib-cli
fints-lib list-accounts \
--url https://banking.example.com/fints \
-n username -p 12345 -b 12345678import { PinTanClient } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
const accounts = await client.accounts();
const balance = await client.balance(accounts[0]);
console.log(`Balance: ${balance.value.value} ${balance.value.currency}`);import { PinTanClient } from "fints-lib";
const client = new PinTanClient({ /* ... */ });
const accounts = await client.accounts();
const endDate = new Date();
const startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000);
const statements = await client.statements(accounts[0], startDate, endDate);
statements.forEach((statement) => {
statement.transactions.forEach((tx) => {
console.log(` ${tx.amount} ${tx.currency} β ${tx.purpose || "N/A"}`);
});
});import { PinTanClient, TanRequiredError } from "fints-lib";
const client = new PinTanClient({ /* ... */ });
const accounts = await client.accounts();
try {
await client.creditTransfer(accounts[0], {
debtorName: "John Doe",
creditor: { name: "Recipient", iban: "DE44500105175407324931", bic: "INGDDEFFXXX" },
amount: 50.0,
remittanceInformation: "Invoice #12345",
});
} catch (error) {
if (error instanceof TanRequiredError) {
const tan = await promptUser(error.challengeText); // your UI
await client.completeCreditTransfer(error.dialog, error.transactionReference, tan, error.creditTransferSubmission);
}
}
β οΈ Experimental. Requires a bank that supports FinTS 4.1 XML.
import { FinTS4Client } from "fints-lib";
import * as readline from "readline";
async function promptTan(challenge: { challengeText?: string; transactionReference: string }): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise((resolve) => {
rl.question(`TAN challenge: ${challenge.challengeText ?? "(no text)"}\nEnter TAN: `, (tan) => {
rl.close();
resolve(tan.trim());
});
});
}
const client = new FinTS4Client({
url: "https://banking.example.com/fints41",
name: "username",
pin: "12345",
blz: "12345678",
tanCallback: promptTan, // called automatically when the bank issues a challenge
});
const accounts = await client.accounts();
// If the bank requires a TAN during the request, `promptTan` is invoked automatically.
const statements = await client.camtStatements(accounts[0]);Some banks use certificates signed by a private CA, or require specific TLS settings. Use
createTlsAgent() to create a custom https.Agent and pass it via fetchOptions:
import { FinTS4Client, createTlsAgent } from "fints-lib";
import fs from "fs";
// Use a custom CA certificate (e.g. a bank-specific private CA)
const agent = createTlsAgent({
ca: fs.readFileSync("/path/to/bank-ca.pem", "utf8"),
});
const client = new FinTS4Client({
url: "https://banking.example.com/fints41",
name: "username",
pin: "12345",
blz: "12345678",
fetchOptions: { agent },
});Security note: Never set
rejectUnauthorized: falsein production. This disables certificate verification entirely and exposes you to man-in-the-middle attacks.
// β
Development / testing only:
const devAgent = createTlsAgent({ rejectUnauthorized: false });
// β Never in production:
// const prodAgent = createTlsAgent({ rejectUnauthorized: false });Note:
createTlsAgent()andfetchOptions.agentare currently only supported byFinTS4Client(FinTS 4.1). The FinTS 3.0PinTanClient/HttpConnectiondoes not expose a custom-agent option; if you need custom TLS for a FinTS 3.0 endpoint, use theNegotiatingClientwithpreferredVersion: "4.1"or configure TLS at the Node.js process level (e.g.NODE_EXTRA_CA_CERTS).
FinTS4Client automatically negotiates the HBCI version with the server:
- It starts with your
preferredHbciVersion(default:"4.1"). - If the server returns error
9010(version not supported), it retries with"4.0", then"3.0". - After the sync phase, the client adopts the highest version both sides support.
const client = new FinTS4Client({
// ...
preferredHbciVersion: "4.1", // start with 4.1, fall back automatically
});- fints-lib β Core library
- fints-lib-cli β Command line interface
- Never log credentials. The library masks PINs and TANs in debug output, but never log the raw config object.
- Store credentials securely. Use environment variables or a secrets manager.
- Use HTTPS only. Always use HTTPS URLs for FinTS endpoints.
- Debug mode. Be cautious with
debug: truein production β it logs full request/response XML.
Report security vulnerabilities privately via GitHub's Security tab instead of opening a public issue.
Authentication Errors:
- Verify username, PIN, and BLZ.
- Some banks require enabling FinTS/HBCI access in online banking settings.
- Check if your bank requires product registration.
TAN Requirements:
- Use
tanCallback(FinTS 4.1) orTanRequiredErrorcatch (FinTS 3.0) to handle TAN challenges.
Timeout Issues:
const client = new PinTanClient({ timeout: 60000, maxRetries: 5 });FinTS 4.1 not working with my bank:
- Most German retail banks use FinTS 3.0 β switch to
PinTanClient. - Check the bank's FinTS URL: some banks have separate endpoints for v3 and v4.
- Prior99/fints β Original repository by Frederick Gnodtke π
- python-fints β Reference implementation
- Open-Fin-TS-JS-Client β Demo server
- mt940-js β MT940 format parser
Contributions in the form of well-documented issues or pull requests are welcome.
- Frederick Gnodtke (Original author)
- Lars Decker (Fork maintainer)
This fork includes several enhancements over the original project:
- β Updated Dependencies: All dependencies updated to their latest stable versions for better security and performance
- β Modern TypeScript: Updated to TypeScript 5.x with improved type safety
- β GitHub Actions: Automated CI/CD pipeline for testing and npm publication
- β Active Maintenance: Regular updates and dependency maintenance
- β
Published as
fints-libandfints-lib-clion npm for easier installation
For end users installing the library:
npm install fints-lib
# or
yarn add fints-libFor the CLI tool:
npm install -g fints-lib-cli
# or
yarn global add fints-lib-cliThis project uses Yarn as the package manager. To set up the development environment:
# Install dependencies
yarn install
# Build all packages
yarn build
# Run tests
yarn test
# Run linting
yarn lintimport { PinTanClient } from "fints-lib";
// Create a client with minimal required configuration
const client = new PinTanClient({
url: "https://banking.example.com/fints", // Your bank's FinTS URL
name: "username", // Your banking username
pin: "12345", // Your banking PIN
blz: "12345678", // Bank code (BLZ/Bankleitzahl)
});
// List all accounts
const accounts = await client.accounts();
console.log(accounts);# Install globally
npm install -g fints-lib-cli
# List your accounts
fints-lib list-accounts \
--url https://banking.example.com/fints \
-n username \
-p 12345 \
-b 12345678import { PinTanClient } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
// Get all accounts
const accounts = await client.accounts();
// Check balance for first account
const balance = await client.balance(accounts[0]);
console.log(`Account: ${accounts[0].iban}`);
console.log(`Balance: ${balance.value.value} ${balance.value.currency}`);CLI Example
fints-lib get-balance \
--url https://banking.example.com/fints \
-n username -p 12345 -b 12345678 \
-i DE89370400440532013000import { PinTanClient } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
const accounts = await client.accounts();
// Fetch last 30 days of transactions
const endDate = new Date();
const startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000);
const statements = await client.statements(accounts[0], startDate, endDate);
// Process transactions
statements.forEach((statement) => {
console.log(`Date: ${statement.date}`);
statement.transactions.forEach((transaction) => {
console.log(` ${transaction.descriptionStructured?.bookingText || "Transaction"}`);
console.log(` Amount: ${transaction.amount} ${transaction.currency}`);
console.log(` Purpose: ${transaction.purpose || "N/A"}`);
});
});CLI Example
# Fetch transactions for a date range
fints-lib fetch-transactions \
--url https://banking.example.com/fints \
-n username -p 12345 -b 12345678 \
-i DE89370400440532013000 \
-s 2024-01-01 -e 2024-12-31 \
--json > transactions.jsonimport { PinTanClient, TanRequiredError } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
const accounts = await client.accounts();
const myAccount = accounts[0];
// Prepare transfer
const transfer = {
debtorName: "John Doe",
creditor: {
name: "Recipient Name",
iban: "DE44500105175407324931",
bic: "INGDDEFFXXX", // Optional for transfers within SEPA
},
amount: 50.0,
remittanceInformation: "Payment for invoice #12345",
};
try {
// Initiate transfer
const result = await client.creditTransfer(myAccount, transfer);
console.log("Transfer successful:", result.taskId);
} catch (error) {
if (error instanceof TanRequiredError) {
// TAN is required - get TAN from user
const tan = "123456"; // Get from user input or TAN app
const result = await client.completeCreditTransfer(
error.dialog,
error.transactionReference,
tan,
error.creditTransferSubmission,
);
console.log("Transfer completed:", result.taskId);
} else {
throw error;
}
}CLI Example
# Transfer money (will prompt for TAN if required)
fints-lib submit-credit-transfer \
--url https://banking.example.com/fints \
-n username -p 12345 -b 12345678 \
--account-iban DE89370400440532013000 \
--debtor-name "John Doe" \
--creditor-name "Recipient Name" \
--creditor-iban DE44500105175407324931 \
--amount 50.00 \
--remittance "Payment for invoice #12345"import { PinTanClient, TanRequiredError } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
const accounts = await client.accounts();
const myAccount = accounts[0];
// Prepare direct debit
const debit = {
creditorName: "My Company GmbH",
creditorId: "DE98ZZZ09999999999", // Your SEPA creditor ID
debtor: {
name: "Customer Name",
iban: "DE02120300000000202051",
},
amount: 99.99,
mandateId: "MANDATE-2024-001",
mandateSignatureDate: new Date("2024-01-15"),
requestedCollectionDate: new Date("2024-12-15"),
remittanceInformation: "Monthly subscription fee",
};
try {
const result = await client.directDebit(myAccount, debit);
console.log("Direct debit submitted:", result.taskId);
} catch (error) {
if (error instanceof TanRequiredError) {
const tan = "123456"; // Get from user
const result = await client.completeDirectDebit(
error.dialog,
error.transactionReference,
tan,
error.directDebitSubmission,
);
console.log("Direct debit completed:", result.taskId);
} else {
throw error;
}
}CLI Example
fints-lib submit-direct-debit \
--url https://banking.example.com/fints \
-n username -p 12345 -b 12345678 \
--account-iban DE89370400440532013000 \
--creditor-name "My Company GmbH" \
--creditor-id DE98ZZZ09999999999 \
--debtor-name "Customer Name" \
--debtor-iban DE02120300000000202051 \
--amount 99.99 \
--mandate-id MANDATE-2024-001 \
--mandate-date 2024-01-15 \
--collection-date 2024-12-15 \
--remittance "Monthly subscription fee"import { PinTanClient } from "fints-lib";
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
});
// Get all accounts with balances
const accounts = await client.accounts();
for (const account of accounts) {
console.log(`\n${account.accountName || "Account"} (${account.iban})`);
console.log(` Type: ${account.accountType || "N/A"}`);
try {
const balance = await client.balance(account);
console.log(` Balance: ${balance.value.value} ${balance.value.currency}`);
console.log(` Available: ${balance.availableBalance?.value || "N/A"}`);
} catch (error) {
console.log(` Balance: Unable to fetch`);
}
}Before using any FinTS library you have to register your application with Die Deutsche Kreditwirtschaft in order to get your registration number. Note that this process can take several weeks. First you receive your registration number after a couple days, but then you have to wait anywhere between 0 and 8+ weeks for the registration to reach your bank's server. If you have multiple banks, it probably reaches them at different times.
This library is maintained in a monorepo using lerna. These packages are included:
- fints-lib - Core library (Take a look for library usage instructions.)
- fints-lib-cli - Command line interface (Take a look for CLI usage instructions.)
This library handles sensitive financial data and credentials. Please follow these security best practices:
- Never log credentials: The library masks PINs and TANs in debug output, but you should never log the raw configuration object
- Store credentials securely: Use environment variables or secure credential stores (e.g., AWS Secrets Manager, Azure Key Vault) instead of hardcoding credentials
- Use HTTPS only: Always use HTTPS URLs for FinTS endpoints to ensure encrypted communication
- Debug mode: Be cautious when enabling debug mode (
debug: true) in production environments, as it logs detailed request/response information
import { PinTanClient } from "fints-lib";
// β
Good: Load from environment variables
const client = new PinTanClient({
url: process.env.FINTS_URL,
name: process.env.FINTS_USERNAME,
pin: process.env.FINTS_PIN,
blz: process.env.FINTS_BLZ,
debug: false, // Disable in production
});
// β Bad: Hardcoded credentials
const badClient = new PinTanClient({
url: "https://example.com/fints",
name: "username",
pin: "12345", // Never hardcode!
blz: "12345678",
});If you discover a security vulnerability, please report it privately via GitHub's "Security" tab instead of opening a public issue.
You can find your bank's FinTS endpoint URL in this community database:
Authentication Errors:
- Verify your username, PIN, and BLZ are correct
- Some banks require you to enable FinTS/HBCI access in your online banking settings
- Check if your bank requires registration (see registration note below)
TAN Requirements:
- Many operations (transfers, direct debits) require TAN authentication
- Use try-catch with
TanRequiredErrorto handle TAN challenges properly - Some banks require TAN even for login - handle with
completeLogin()
Timeout Issues:
- Increase the timeout value in client configuration:
const client = new PinTanClient({ // ... other config timeout: 60000, // 60 seconds maxRetries: 5, });
Date Range Queries:
- Not all banks support all date ranges - some limit how far back you can query
- Use shorter date ranges if you experience timeouts or errors
-
Always use environment variables for credentials:
const client = new PinTanClient({ url: process.env.FINTS_URL, name: process.env.FINTS_USERNAME, pin: process.env.FINTS_PIN, blz: process.env.FINTS_BLZ, });
-
Enable debug mode during development:
const client = new PinTanClient({ // ... credentials debug: process.env.NODE_ENV === "development", });
-
Handle errors gracefully:
import { FinTSError, TanRequiredError, PinError } from "fints-lib"; try { const accounts = await client.accounts(); } catch (error) { if (error instanceof TanRequiredError) { // Handle TAN requirement } else if (error instanceof PinError) { // Handle PIN error } else if (error instanceof FinTSError) { console.error(`FinTS Error [${error.code}]: ${error.message}`); } }
-
Close dialogs when done:
const dialog = await client.startDialog(); try { // ... perform operations } finally { await dialog.end(); }
FinTS is a complex and old format and this library wouldn't have been possible without the great work of:
- Prior99/fints - The original repository by Frederick Gnodtke that this fork is based on. Thank you for creating such a comprehensive and well-structured FinTS implementation! π
- python-fints was used a reference implementation.
- Open-Fin-TS-JS-Client provides a demo server used for testing this library.
- mt940-js is used internally for parsing the MT940 format.
Contributions in the form of well-documented issues or pull-requests are welcome.
- Frederick Gnodtke (Original author)
- Lars Decker (Fork maintainer)