GitHub - MugenLab/ledgerbridge: Convert bank statements (OFX/QFX, QIF, CSV, CAMT.053) into beancount, hledger and Firefly III imports. Offline, zero dependencies, with deduplication and balance checks. · GitHub
Skip to content

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

ledgerbridge

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.


Contents


What it reads and writes

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.

Install

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/ledgerbridge

Or run it without installing anything permanently:

uvx --from git+https://github.com/MugenLab/ledgerbridge ledgerbridge inspect statement.ofx

Not 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 --help

Firefly III: importing OFX / QFX

Firefly 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)

Two files instead of the mapping step

--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.

Or map the columns by hand

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 iban and leaves account_number empty; a US OFX file fills account_number with something like 4455667788 and leaves iban empty. 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.php and app/Services/Shared/Configuration/Configuration.php, branch main), 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 in docs/qa/2026-08-30-firefly-csv-check.md in the company repo.
  • external_id is b:<bank reference> when the bank supplied one (OFX FITID, CAMT AcctSvcrRef), and h:<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.

CAMT.053 to beancount

$ 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 EUR

Details worth knowing:

  • The counter-account is always a placeholder — Expenses:Unknown or Income: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 balance directive 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-ref is the bank's own reference, copied verbatim (OFX FITID, CAMT AcctSvcrRef, or a CSV id column); it is absent when the bank gave none. lb-uid is ledgerbridge's own identity for the transaction — the same value as external_id in 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.

Balance assertions and the bootstrap block

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 assertions pass.

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.

QIF to hledger

ledgerbridge convert quicken-export.qif --to hledger --currency EUR --account Assets:Bank:Current

QIF 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 EUR

Balance reconciliation: the part other converters skip

opening + 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 $?
1

Which 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)

Re-importing overlapping months

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.

CSV bank profiles

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/profiles

Matching is automatic: if a CSV's header contains all of a profile's fingerprint columns, that profile is used without --profile.

How the five profiles were checked, and what that is worth

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's Payer 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.csv

Compare 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.

Your bank is not in the list

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.toml

The 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 beancount

Drop 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.

What ledgerbridge does not do

A short list of what you should not expect, so you can stop reading now if one of these is what you came for.

PDF statements — not supported, and not planned

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.

Everything else it does not do

  • No categorisation. Every counter-account is Expenses:Unknown or Income: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 --quiet does not suppress, and inspect shows it under warnings:.
  • 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.

Nothing leaves your machine

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 = [] in pyproject.toml, and the package is stdlib-only — so there is no transitive supply chain to audit, and pipx run ledgerbridge does not resolve a single wheel beyond ledgerbridge itself.
  • Writes only where you point it. Standard output, the -o path you gave, and the --since-state path you gave. Nothing else. inspect writes nothing at all.

You can verify all of that: it is ~4,500 lines of Python and you can read it.

Command reference

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 hledger

Environment: LEDGERBRIDGE_PROFILE_DIR overrides where user profiles are read from (default $XDG_CONFIG_HOME/ledgerbridge/profiles, i.e. ~/.config/ledgerbridge/profiles).

Exit codes

The exit codes are part of the interface, because this tool belongs in a Makefile.

Code Meaning
0 Success.
1 Balance reconciliation failed and --allow-unbalanced was not given. Nothing was written.
2 Input format not recognised.
3 Input error: unreadable file, broken record, unusable profile.
import: statement.xml
	ledgerbridge convert $< --to beancount --account Assets:Bank:Checking -o books/$(shell date +%Y-%m).beancount

If the bank's export is short a few rows, that make target fails instead of quietly poisoning your ledger.

Contributing

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 -q

License

MIT.

About

Convert bank statements (OFX/QFX, QIF, CSV, CAMT.053) into beancount, hledger and Firefly III imports. Offline, zero dependencies, with deduplication and balance checks.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages