Your bank hands you an OFX, QFX, QIF or CAMT.053 file. Firefly III, beancount and hledger want something else. ledgerbridge converts one into the other — entirely offline — and refuses to hand you a file whose balances do not add up.
One command, no dependencies, no account credentials, no upload.
$ ledgerbridge convert tests/fixtures/bank_ofx2.ofx --to firefly-csv
ledgerbridge: 2 transaction(s) written as firefly-csv; reconciliation skipped: the file reports no opening balance (ofx statements do not always carry one)
date,description,amount,currency,iban,account_number,external_id,notes
2026-02-03,DUNNES STORES - Card purchase,-15.75,EUR,IE29AIBK93115212345678,,b:X-2026-02-03-001,
2026-02-10,CLIENT LTD - Invoice 2026-02,1200.50,EUR,IE29AIBK93115212345678,,b:X-2026-02-10-002,That is the real output of that command on the sample file in this repository. The summary line goes to stderr, the CSV to stdout, so > import.csv gives you just the data.
- What it reads and writes
- Install
- Firefly III: importing OFX / QFX
- CAMT.053 to beancount
- QIF to hledger
- Balance reconciliation
- Re-importing overlapping months
- CSV bank profiles
- What ledgerbridge does not do
- Nothing leaves your machine
- Command reference
- Exit codes
- Contributing
| Input | Notes |
|---|---|
| OFX 1.x (SGML) | The unclosed-tag dialect most banks actually emit. Parsed directly, no third-party OFX library. |
| OFX 2.x / QFX (XML) | Same code path. .qfx is treated by content, never by extension. |
| QIF | Day-first vs month-first is decided from every date in the file, not the first one. |
| CAMT.053 (ISO 20022) | camt.053.001.02 through .08. Namespace URIs are ignored; matching is by local element name. |
| CSV | Driven by a small TOML profile per bank. Five profiles ship with the package; you can generate your own in one command. |
| Output | Notes |
|---|---|
firefly-csv |
A flat CSV — date, description, amount, currency, iban, account_number, external_id, notes — for the Firefly III data importer. With --firefly-config it also writes the importer's configuration file, which skips the column-mapping step entirely. |
beancount |
Transactions plus balance assertions, with a commented bootstrap block so the file also loads standalone. |
hledger |
A journal that passes hledger check assertions, with the same commented bootstrap block. |
Format detection looks at the bytes, not the filename. Banks ship .qfx files containing XML, .xml files containing OFX, and .csv files containing anything at all.
Requires Python 3.10 or newer. There are no runtime dependencies — the whole package is the standard library.
pipx install git+https://github.com/MugenLab/ledgerbridge # recommended
uv tool install git+https://github.com/MugenLab/ledgerbridge
pip install git+https://github.com/MugenLab/ledgerbridgeOr run it without installing anything permanently:
uvx --from git+https://github.com/MugenLab/ledgerbridge ledgerbridge inspect statement.ofxNot on PyPI yet, hence the git+ prefix. When it lands there this becomes plain pipx install ledgerbridge.
From a clone:
git clone https://github.com/MugenLab/ledgerbridge
cd ledgerbridge
python -m pytest -q
PYTHONPATH=src python -m ledgerbridge --helpFirefly III's data importer takes CSV, camt.053 and a few bank APIs. It does not take OFX or QFX, and its documentation is explicit that PDF will never be supported. If your bank's only export is .ofx or .qfx, you are stuck at the front door.
$ ledgerbridge convert statement.ofx --to firefly-csv \
-o firefly-import.csv --firefly-config firefly-import.json
ledgerbridge: 4 transaction(s) written as firefly-csv; importer configuration in firefly-import.json; reconciliation skipped: the file reports no opening balance (ofx statements do not always carry one)--firefly-config writes the JSON configuration file the data importer accepts on
its upload page, right next to the CSV. Upload both, and the column-role screen
arrives already filled in: eight dropdowns you do not have to touch, the date format
already set to Y-m-d, and the mapping step switched off. The only thing left for
you is picking the asset account this statement belongs to — that id lives in your
Firefly instance, so we cannot know it.
The configuration is generated from the rows that were just written, not from a
template. That matters for one specific reason: exactly one of iban /
account_number is empty in any given file, and both roles run a validator over
every line they are given. So the empty one is set to _ignore — automatically, per
file, without you having to notice. Any other column that turned out empty (a
statement with no notes, say) is left out the same way.
"roles": [
"date_transaction", "description", "amount", "currency-code",
"_ignore", "account-number", "external-id", "note"
]That is a US OFX file: the account identity went to account-number and the iban
column, empty, was left unmapped.
The flag needs --to firefly-csv; asking for it with any other output format is an
error rather than a file that quietly describes the wrong thing.
If you would rather not upload a configuration file, the table below is that same mapping step written out. The right-hand roles are the ones the data importer actually defines.
| Column | Map it to role | What is in it |
|---|---|---|
date |
date_transaction |
Booking date, YYYY-MM-DD. Set the importer's date format to Y-m-d. |
description |
description |
Payee and memo joined with -, deduplicated when the bank puts the same string in both. |
amount |
amount |
Signed from your point of view: negative is money leaving the account. Decimal string, . as the decimal mark, no thousands separators. Do not use the amount_negated or debit/credit roles — the sign is already right. |
currency |
currency-code |
From the file (CURDEF, Ccy, or a CSV currency column), or whatever you pass to --currency. |
iban |
account-iban |
The account identity from the file, but only when it has the shape of an IBAN. Empty otherwise. |
account_number |
account-number |
The account identity when it is not an IBAN — a US account number, a masked card number. Empty when iban is filled. Exactly one of the two columns carries a value. |
external_id |
external-id |
The stable per-transaction identity (see below), so Firefly's own duplicate detection has something to key on. |
notes |
note |
Cheque number, value date, category and free-text memo, joined with ; . |
Three honest caveats:
- The account identity is split across two columns because Firefly validates whatever you map to its IBAN role, and a US account number mapped there is rejected at import. So a European statement fills
ibanand leavesaccount_numberempty; a US OFX file fillsaccount_numberwith something like4455667788and leavesibanempty. Map the one your bank actually produced, and leave the empty one unmapped — the IBAN role runs a validator over whatever you point at it, and an all-empty column has no business going through it. ledgerbridge does not invent an IBAN for accounts that have none. - These roles, and the keys in the generated configuration file, were read off the data importer's own sources on 2026-08-30 (
config/csv.phpandapp/Services/Shared/Configuration/Configuration.php, branchmain), not off a live import: running one needs a PHP install and a database, which we do not have. Every column has a role that fits it and no value lands in a validator in the wrong shape — but if your importer version disagrees, believe your importer. The full column-by-column check is indocs/qa/2026-08-30-firefly-csv-check.mdin the company repo. external_idisb:<bank reference>when the bank supplied one (OFXFITID, CAMTAcctSvcrRef), andh:<20 hex chars>when it did not — a SHA-256 over the date, exact amount, normalised description, and the occurrence number of that exact triple within its day. QIF has no per-transaction id at all, so QIF always gets the hash form.
$ ledgerbridge convert tests/fixtures/camt053_v08.xml --to beancount --account Assets:Bank:ABN
ledgerbridge: warning: entry #5 skipped: status PDNG (not booked)
ledgerbridge: 4 transaction(s) written as beancount; reconciled: 2189.35 + -215.25 = 1974.10 EUR
; Generated by ledgerbridge 0.1.0
; Source: camt053_v08.xml (camt.053)
; Account: Assets:Bank:ABN [EUR]
; IBAN: NL91ABNA0417164300
; The counter-account of every posting is a placeholder - categorise
; them in your editor or with a beancount plugin.
; Starting a brand new ledger with just this file? Uncomment the block
; below: it opens the accounts and books the bank's opening balance
; against equity, which is what the balance assertions check against.
; If you are appending to an existing ledger, leave it commented out.
; 2026-01-31 open Assets:Bank:ABN EUR
; 2026-01-31 open Expenses:Unknown EUR
; 2026-01-31 open Income:Unknown EUR
; 2026-01-31 open Equity:Opening-Balances EUR
; 2026-01-31 * "ledgerbridge" "Opening balance"
; Assets:Bank:ABN 2189.35 EUR
; Equity:Opening-Balances
; opening balance reported by the bank at the start of this day
2026-02-01 balance Assets:Bank:ABN 2189.35 EUR
2026-02-04 * "Albert Heijn 1234" "Boodschappen week 6"
bank-ref: "NL2026020400001"
lb-uid: "b:NL2026020400001"
value-date: 2026-02-05
Assets:Bank:ABN -215.25 EUR
Expenses:Unknown
2026-02-10 * "Rente Bank" "Rente"
bank-ref: "NL2026021000002"
lb-uid: "b:NL2026021000002"
Assets:Bank:ABN 0.10 EUR
Income:Unknown
... two more transactions ...
; closing balance as of end of 2026-02-28 (asserted at the start of the next day)
2026-03-01 balance Assets:Bank:ABN 1974.10 EURDetails worth knowing:
- The counter-account is always a placeholder —
Expenses:UnknownorIncome:Unknown, chosen by sign. ledgerbridge does not guess categories. It leaves the placeholder leg without an amount so beancount balances it and a later edit only touches one number. - The closing assertion is dated the day after the closing date. A beancount
balancedirective is evaluated at the start of its date, while a bank's closing balance is as of the end of its date. Asserting on the closing date itself is the classic off-by-one that makes a correct import look broken. - Pending entries (
PDNG,INFO) are skipped, and you are told about it. - Two metadata keys are attached to each transaction.
bank-refis the bank's own reference, copied verbatim (OFXFITID, CAMTAcctSvcrRef, or a CSV id column); it is absent when the bank gave none.lb-uidis ledgerbridge's own identity for the transaction — the same value asexternal_idin the Firefly III output, and what deduplication matches on across runs. - One
<Ntry>becomes exactly one transaction, even when it carries several<TxDtls>. Splitting a batch booking into its details would double-count it against the reported balances.
Pass --no-balances if you only want transactions and no assertions.
Both ledger writers put the bank's closing balance in the file as an assertion, so your tooling — not your eyes — catches a row that went missing on the way in. An assertion is only worth anything if the account starts from the right number, so above the transactions sits a commented-out opening entry:
- Appending to a ledger that already carries this account? Leave it commented. Your existing history supplies the starting balance and the closing assertion checks against it.
- Starting a fresh ledger from this one file? Uncomment it, and
bean-check/hledger check assertionspass.
OFX and QFX carry no opening balance at all, so for those the figure is derived — closing minus the transactions listed — and labelled (derived) wherever it appears. It makes the file internally consistent; it cannot catch a row the export never included, and ledgerbridge never counts it as a reconciliation. When even that is impossible (the closing balance predates the last transaction), no assertion is written and the file says why instead of shipping one that fails.
ledgerbridge convert quicken-export.qif --to hledger --currency EUR --account Assets:Bank:CurrentQIF has no specification worth the name and two incompatible date conventions. 03/04/2026 is a coin flip, and getting it wrong is silent. ledgerbridge votes across every date in the file. If no date in the file has a day above 12, it falls back to month-first and says so out loud:
ledgerbridge: warning: every date in this file is ambiguous (no day > 12); assumed month-first (US) order - check a known transaction before importing
QIF carries no currency, so pass --currency. It carries no balances, so reconciliation is always skipped. It carries no account identity, so if you keep state files (below) give each account its own --account value.
The same works for CSV and OFX inputs:
$ ledgerbridge convert tests/fixtures/wise.csv --to hledger --account Assets:Wise
ledgerbridge: 3 transaction(s) written as hledger; reconciled: 1500.00 + 1166.61 = 2666.61 EUR
; Generated by ledgerbridge 0.1.0
; Source: wise.csv (csv)
; Account: Assets:Wise [EUR]
2026-01-05 * opening balance reported by the bank
Assets:Wise 0 EUR = 1500.00 EUR
2026-01-05 * Lidl Berlin | Card transaction of 23.40 EUR issued by Lidl Berlin
; bank-ref: CARD-1029384
; lb-uid: b:CARD-1029384
Assets:Wise -23.40 EUR
Expenses:Unknown
... two more transactions ...
2026-01-21 * closing balance reported by the bank
Assets:Wise 0 EUR = 2666.61 EURopening + sum(transactions) == closing, to the cent, in Decimal. Zero tolerance — a tolerance would only hide the bug we are looking for. No float touches a monetary value anywhere in this package.
This matters because the failure it catches is invisible. A statement that silently drops three rows still imports fine, still looks fine, and quietly puts your books out by a few hundred until you notice months later.
When the arithmetic fails, ledgerbridge writes nothing and exits 1:
$ ledgerbridge convert tests/fixtures/camt053_unbalanced.xml --to beancount
ledgerbridge: balance reconciliation failed
opening balance 1500.00 EUR
+ transactions -1760.65 EUR
= computed closing -260.65 EUR
closing balance 2189.35 EUR
difference -2450.00 EUR
The statement does not add up. Usually one of:
- the export is missing rows (date range cut mid-day),
- amounts were parsed with the wrong decimal mark,
- a debit/credit column was read with the wrong sign.
Run 'ledgerbridge inspect <file>' for the parsed numbers, or
pass --allow-unbalanced to convert anyway.
$ echo $?
1Which formats can actually be reconciled — this is a property of the file, not of ledgerbridge:
| Format | Reconciled? |
|---|---|
| CAMT.053 | Yes. Carries OPBD/PRCD and CLBD/ITBD. |
| CSV with a running-balance column | Yes, if the profile maps balance and the column is filled on every row. Opening is derived as first running balance − first amount, which makes the check validate every row in between. |
| OFX / QFX | No. OFX carries LEDGERBAL (a closing balance) but no opening balance, so the check is reported as skipped, not as passed. |
| QIF | No. QIF has no balances at all. |
| CSV without a balance column | No. |
A skipped check is reported honestly as skipped. ledgerbridge never claims to have verified something it could not verify.
ledgerbridge inspect shows you all the parsed numbers without writing anything:
$ ledgerbridge inspect tests/fixtures/camt053_v08.xml
file format: camt.053
account IBAN: NL91ABNA0417164300
currency: EUR
transactions: 4
period: 2026-02-04 .. 2026-02-20
money in: 0.30
money out: -215.55
net change: -215.25
opening balance: 2189.35 as of 2026-02-01 [PRCD]
closing balance: 1974.10 as of 2026-02-28 [CLBD]
available balance:1874.10 as of 2026-02-28 [CLAV]
bank references: 4 of 4 transactions carry one (the rest get a content hash)
reconciliation: OK (reconciled: 2189.35 + -215.25 = 1974.10 EUR)
warnings:
- entry #5 skipped: status PDNG (not booked)Statements for neighbouring months overlap, and re-importing an overlap is how people end up with the same rent payment three times. --since-state keeps a small JSON file of what has already been emitted:
$ ledgerbridge convert january.qif --to firefly-csv --since-state .lb-state.json -o jan.csv
ledgerbridge: 4 transaction(s) written as firefly-csv; reconciliation skipped: ...
$ ledgerbridge convert january.qif --to firefly-csv --since-state .lb-state.json -o again.csv
ledgerbridge: 0 transaction(s) written as firefly-csv; 4 already in .lb-state.json; reconciliation skipped: ...
$ ledgerbridge convert jan-feb-overlap.qif --to firefly-csv --since-state .lb-state.json -o feb.csv
ledgerbridge: 1 transaction(s) written as firefly-csv; 2 already in .lb-state.json; reconciliation skipped: ...How the identity survives a re-export:
- The bank's own reference when there is one, prefixed
b:so it can never collide with a computed one. - Otherwise
h:plus a SHA-256 over date, exact amount, a normalised description (accents folded, punctuation and case and whitespace ignored, because banks re-wrap and re-case the same description between exports) and the occurrence number of that exact triple within its day. Counting per identical triple rather than per day means adding an unrelated transaction to the same day does not renumber — and therefore does not re-import — the others.
The state file is namespaced by the account identity printed in the file (IBAN, then account id). Files that carry no identity — QIF, most CSV exports — fall back to your --account value, so give each account a distinct one.
The state file is only written after the output exists, and via write-then-rename. A state file claiming to have exported transactions nobody has is worse than no state file.
Every bank's CSV is its own dialect. A profile is a ~40-line TOML file describing one: delimiter, encoding, date formats, decimal mark, and which column means what.
$ ledgerbridge profiles --list
NAME STATUS DESCRIPTION
dkb verified DKB Girokonto CSV export (2023 layout and the pre-2023 one)
n26 verified N26 CSV export (2023 layout and the older one)
revolut verified Revolut personal account CSV export
sparkasse-camt verified Sparkasse CSV-CAMT export (German savings banks)
wise verified Wise (formerly TransferWise) balance statement CSV
verified means the column mapping was checked against a real statement or
against another importer that reads one; each profile records which in its
source_note. It is still worth comparing your first import with your bank.
User profiles are read from /home/you/.config/ledgerbridge/profilesMatching is automatic: if a CSV's header contains all of a profile's fingerprint columns, that profile is used without --profile.
Until 2026-08-31 every profile here was built from the bank's public documentation and shipped marked UNVERIFIED. On that date each one was compared, column by column, against a separately maintained open-source importer for the same bank — code that other people run against their own real exports. Each profile's source_note names the project, the file and the commit it was checked against.
Three of the five were wrong. That is the part worth reporting, because it is what the check was for:
- DKB hands out its 2023 export with
;for some customers and,for others, under a byte-identical header. The profile knew only;, so roughly half of DKB users would have been told their file had no recognisable columns. - DKB and Wise both fill two counterparty columns and leave it to the reader to pick the right one. On money coming in, the other party is the sender — DKB's
Zahlungspflichtige*r, Wise'sPayer Name. The profiles read the receiver instead, which on your own salary is you. The output was valid, imported cleanly, and named the wrong party. This is exactly the failure mode that gets reconciled around for months. - N26 ships two layouts that share not one column name. Only the 2023 one was recognised; the older export fell through to "this CSV does not match any known bank profile".
Sparkasse CSV-CAMT and Revolut matched name for name and needed no change.
What "verified" here does not mean. It does not mean a maintainer ran your bank's own file. Nobody here has a DKB account. It means the mapping agrees with an independent implementation that does read real files, and that a statement in each layout converts end to end in the test suite. Column names still change between export versions, banks still localise headers, and a mapping wrong by one column still produces a file that imports cleanly and is wrong.
So the first import is still worth a minute:
ledgerbridge inspect my-export.csvCompare against your bank's own screen: the transaction count, the earliest and latest date, and — if the profile maps a running-balance column — the opening and closing balance. Then eyeball two or three amounts, one money in and one money out, to catch an inverted sign or a swapped counterparty. If those match, the mapping is right for your file.
If they do not, fix it and send it back: docs/PROFILES.md#sending-a-corrected-profile.
That is the normal case; the long tail of banks is not something one author can cover. Generate a draft in one command:
$ ledgerbridge profiles --guess my-bank-export.csv > my-bank.tomlThe draft is a real, commented TOML file with the guesses it made, the samples it saw in each column, the columns it could not place, and TODO markers where it is unsure. It skips metadata lines above the header, picks the delimiter and decimal mark, works out whether the file uses one signed amount column or separate debit/credit columns, and confirms a running-balance column against the amounts before mapping it.
Then use it:
ledgerbridge inspect my-bank-export.csv --profile ./my-bank.toml
ledgerbridge convert my-bank-export.csv --profile ./my-bank.toml --to beancountDrop it in ~/.config/ledgerbridge/profiles/ and it is picked up automatically, by fingerprint, from then on. A user profile with the same name as a built-in replaces it — which is how you fix a wrong mapping without waiting for a release.
Full field reference, and how to send a correction back: docs/PROFILES.md.
A short list of what you should not expect, so you can stop reading now if one of these is what you came for.
This is a deliberate decision, not a missing feature.
Parsing a PDF statement means reverse-engineering a layout. It works beautifully on the file you tested with and then, on the long tail of column drift, wrapped descriptions, multi-page tables, footers that look like rows and rows that look like footers, it silently produces a plausible wrong number. In accounting, a quietly incorrect line is strictly worse than a missing one: a missing line gets noticed, a wrong one gets reconciled around for months.
Firefly III's own documentation reaches the same conclusion and states that PDF files will never be supported. We agree, and we would rather solve the part that can be solved deterministically — OFX, QIF, CAMT.053 and CSV all have machine-readable structure, and for CAMT and balance-carrying CSV we can even prove the result adds up.
If PDF is your only export, ask your bank for CSV or CAMT.053. In the EU, most banks have one; it is often two clicks further into the UI.
Hand it a PDF anyway and it says so in one line and exits with code 2 — same for .xlsx, .xls, a zipped export or a download that got cut off. It looks at the bytes, not the extension, so a PDF named statement.csv is still caught. Files that are text in a wider encoding are not caught by that check: Excel's "Unicode Text" export (UTF-16 with a byte-order mark) is read normally.
- No categorisation. Every counter-account is
Expenses:UnknownorIncome:Unknown. There is no rules engine, no payee-matching, no machine learning. Categorise in your editor, or with a beancount/hledger plugin that already does it well. - No bank connection. ledgerbridge never talks to your bank. It converts a file you already downloaded. No OFX Direct Connect, no PSD2, no screen scraping, no credentials.
- No upload to Firefly III. It writes a CSV, and optionally the importer's configuration file to go with it; you still run the Firefly III data importer yourself. There is no API client.
- No currency conversion. Foreign amounts pass through exactly as the bank stated them. No FX rates, no fetched prices.
- No transfer matching. Two sides of a transfer between your own accounts stay two independent transactions.
- No MT940 / MT942, no CAMT.052 or CAMT.054. Only CAMT.053.
- No ledger, hledger-CSV-rules, GnuCash or Excel output. Three writers, that is all.
- No GUI, no web UI, no daemon. One CLI.
- QIF splits are flattened into a single amount, with the split categories kept as a note and a warning printed.
- Non-cash QIF sections (
!Type:Invst,!Type:Cat, …) are skipped with a warning. Only cash-like accounts are converted. - An empty result is announced, never silent. When a file parses but yields no transactions — a truncated OFX, a CAMT.053 carrying only balances, a QIF holding nothing but
!Type:Invst— the output is still written, with zero transactions in it, and a warning naming the file, the detected format and the likely cause goes to stderr:statement.ofx: read as ofx, but it contains no transactions - the download may be truncated, or the bank may have returned an empty date range. Nothing will be imported.An empty month is a legitimate result, so this is a warning and the exit code stays 0. It is the single message--quietdoes not suppress, andinspectshows it underwarnings:. - One account per file. A file containing statements for several different accounts is refused rather than merged; run ledgerbridge once per account. Several statement pages for the same account are merged.
A bank statement is one of the most sensitive files most people own. So:
- No network calls. Not for updates, not for exchange rates, not for anything. There is no HTTP client in the package.
- No telemetry, no analytics, no crash reporting.
- No runtime dependencies.
dependencies = []inpyproject.toml, and the package is stdlib-only — so there is no transitive supply chain to audit, andpipx run ledgerbridgedoes not resolve a single wheel beyond ledgerbridge itself. - Writes only where you point it. Standard output, the
-opath you gave, and the--since-statepath you gave. Nothing else.inspectwrites nothing at all.
You can verify all of that: it is ~4,500 lines of Python and you can read it.
ledgerbridge convert FILE --to {beancount,hledger,firefly-csv} [options]
| Option | Meaning |
|---|---|
--to FORMAT |
Required. beancount, hledger or firefly-csv. |
--account NAME |
Ledger account for this statement. Default Assets:Bank:Checking. |
--currency CODE |
Override the currency; by default it is taken from the file. Required when the file states none (QIF, most CSV). |
--profile NAME|PATH |
CSV profile name or path to a .toml. CSV only. |
--format FMT |
Force the input format: ofx, qif, camt.053, csv. Skips detection. |
--since-state PATH |
Skip transactions already recorded in this state file, then update it. |
--allow-unbalanced |
Convert even when the balances do not add up. |
--no-balances |
Do not emit balance assertions in the output. |
--firefly-config PATH |
Also write the Firefly III data importer's JSON configuration for this CSV, so its column-mapping step can be skipped. --to firefly-csv only. |
-o, --output PATH |
Write to a file instead of stdout. An unwritable path (missing directory, no permission) is an error, never a silent fallback to stdout; an empty -o "" is rejected too. |
-q, --quiet |
Suppress the summary and warnings on stderr. The empty-file warning below is the one exception — it is always printed. |
ledgerbridge inspect FILE [--format FMT] [--profile NAME|PATH]
Describes what ledgerbridge sees in a file — format, account, currency, transaction count, period, money in/out, balances, reconciliation, warnings. Writes nothing to disk. Run this first, always.
ledgerbridge profiles --list
ledgerbridge profiles --guess FILE [--name NAME]
FILE may be - to read from stdin:
cat statement.ofx | ledgerbridge convert - --to hledgerEnvironment: LEDGERBRIDGE_PROFILE_DIR overrides where user profiles are read from (default $XDG_CONFIG_HOME/ledgerbridge/profiles, i.e. ~/.config/ledgerbridge/profiles).
The exit codes are part of the interface, because this tool belongs in a Makefile.
import: statement.xml
ledgerbridge convert $< --to beancount --account Assets:Bank:Checking -o books/$(shell date +%Y-%m).beancountIf the bank's export is short a few rows, that make target fails instead of quietly poisoning your ledger.
The single most useful thing you can send is a corrected or new CSV profile — see docs/PROFILES.md#sending-a-corrected-profile. It takes one command to generate and it removes a bank from the long tail for everyone else.
The second most useful is an anonymised statement file that ledgerbridge gets wrong. Replace names and account numbers with fake ones, keep the structure and the amounts, and open an issue. Never attach a real statement — replace the identifying data first.
python -m pytest -qMIT.
