
LedgerLou Bank
The Bank module connects raw account movements with the general ledger. Bank statements are imported, matched against suggestions, and systematically reconciled via REST or agents.
API ReferenceWorkflow
POST /v1/bank-accounts//upload. Duplicates are automatically skipped based on a content hash.unmatched shows where action is still required.GET /v1/bank-transactions/suggestions returns prioritized suggestions based on amount, date, and reference text. Confirmation happens via REST or via an agent with write clearance.GET /v1/bank-accounts//reconciliation shows open vs. reconciled transactions and the account’s current posting status.Access methods
Foreign-currency reconciliation
LedgerLou continues to keep the general ledger in EUR. For open items in a foreign currency, the system therefore always stores two levels:
- Document currency: e.g.
41.63 USD - EUR posting value at initial posting: e.g.
38.27 EUR
During bank reconciliation:
- Matching suggestions for foreign-currency invoices compare the bank movement with the EUR posting value, not with the raw foreign-currency figure.
- The open item is reduced or closed in document currency during reconciliation.
- The difference between the actual bank inflow/outflow and the cleared EUR posting value is booked as realized FX gain/loss.
- The REST API offers an explicit FX settlement with document amount, EUR clearing, and FX effect.
Group reconciliation and residual differences
In addition to the existing 1:1 assignment, LedgerLou now supports a raw match-group flow for open items: one or more bank transactions against one or more OIs, optionally supplemented by explicit clearing lines.
- Endpoint:
POST /v1/bank-match-groups - Goal: consolidate open items in a single
Settlement-Intent, including1:1cases in the dashboard - All selected bank transactions must originate from the same bank account and have the same direction.
- The group may be cleared via three building blocks: allocations to OIs, automatic FX gain/loss posting, and manual clearing lines.
- The result is a single settlement posting in the general ledger; all associated bank transactions are set to the same
intent_id. POST /v1/bank-match-groups/:id/unmatchfully reverses this group posting and reopens all linked bank transactions.
For foreign-currency open items, each allocation is still reconciled against the EUR posting value. The difference between the bank amount and the EUR clearing flows automatically via 4840/6880. Other residual differences such as bank fees, cash discount, write-offs, or manual counter-postings are captured as explicit clearing lines with a freely chosen account and debit/credit side.
Duplicate detection
When importing a bank statement, LedgerLou checks each transaction in three stages before it is written to the database.
Stage 1 — Bank reference (highest priority)
If the transaction contains a bank_reference (e.g. an order or transaction ID from the bank), the system first searches for a row with an identical normalized bank reference and the same amount. Hit → duplicate, row is skipped.
Stage 2 — Content fingerprint
Candidates with the same amount and a matching date (posting or value date ±0) are checked:
- IBAN of the counterparty + reference text match → duplicate
- Name of the counterparty + reference text match → duplicate
All fields are normalized before comparison: Unicode composition resolved, umlauts simplified (ü → u), special characters replaced with spaces, lowercased. This way, e.g. "Müller & Söhne" and "Muller und Sohne" are recognized as identical.
Stage 3 — Content hash (database constraint)
In parallel, the parser computes a SHA-256 hash over the fields:
tenant_id | bank_account_id | tx_date | amount | counterparty_name | reference
The hash is enforced via UNIQUE(tenant_id, content_hash). If no hit is found in stages 1 and 2, this constraint prevents a duplicate insert at the database level (ON CONFLICT DO NOTHING).
API response on upload
POST /v1/bank-accounts/:id/upload always returns HTTP 201 — regardless of whether duplicates were included. The response body contains a detailed breakdown:
{
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"total_rows": 50,
"imported": 47,
"skipped_duplicates": 3,
"errors": []
}
| Field | Meaning |
|---|---|
batch_id | UUID of this import run |
total_rows | Number of rows processed |
imported | Transactions actually saved as new |
skipped_duplicates | Detected and silently skipped duplicates |
errors | Parse or validation errors per row |
Duplicates are never treated as errors — they only increase skipped_duplicates. Re-uploading the same bank statement is therefore always safe.