Your general ledger islocked inside the ERP.

Ledger is a double-entry shadow ledger. It mirrors your system of record as correct double-entry — so you can finally query it, reconcile it, and report on it.

Read this first
This is not your book of record — on purpose.

Statutory numbering and period close stay upstream, where the auditors already look. Ledger is the copy you can ask questions of, without touching the one you cannot.

The numbers exist. Reaching them is the problem.

Every finance team has lived some version of this.

The GL is locked behind the ERP

ACDOCA has the answer. Getting it out means a transport, a functional consultant, and a two-week queue.

Nobody can query it

Finance asks a question, IT writes a report, the report is wrong, repeat. The data was never the bottleneck.

Exports are flat and lossy

A CSV dump loses the tree, loses the double-entry, and arrives as one wide table nobody can reconcile.

Reports only exist at close

A trial balance you can only see on the fifth working day is not a control. It is a postmortem.

Every copy drifts

The spreadsheet, the BI extract, the deck — three numbers for the same month, none of them traceable back.

Nothing checks it balances

Once the data leaves the ERP, no invariant follows it. Unbalanced rows are found by eye, or not at all.

The ERP is not wrong. It is just not answering.

The whole model

Three tables. No fourth concept.

A transaction is a dated fact. Its entries are how it lands on accounts. The entries' debits equal their credits. Everything else follows from that sentence.

accounts
The buckets

Five types, a parent tree, and nothing cached. Normal balance and statement placement are derived from type, never stored — so no two rows can disagree.

transactions
The event

One business fact — date, narration, status, and source_ref back to the upstream document. It carries no amount of its own.

entries
The lines

One account's share of a transaction. Unsigned debit and credit in minor units, exactly one non-zero, summing to equality across the transaction.

A mirror is no excuse for a wrong ledger.

Postgres can say “this row is valid” but not “these rows sum correctly.” So the invariants live on the write path, and every caller goes through them.

Debits equal credits

Enforced when a transaction is posted, in one database transaction, all-or-nothing. There is no path that writes a half-balanced fact.

Groups never carry entries

A heading is a rollup, not a bucket. Post to the leaves; the tree adds itself up.

Posted rows are immutable

draft → posted → void. A posted transaction is corrected by another transaction, the way books actually work.

Errors carry figures

A refused write returns total_debit, total_credit and the difference — usually the missing line — not an adjective.

Same ledger. Every door.

Most writes come from importers and agents. Most reads come from people looking at reports. Both are first-class.

Chart of accounts

The full tree — groups and leaves, five types, rolled-up totals that never double-count.

Statements

Trial balance, P&L, balance sheet, cash flow. Computed from entries, on demand, at any date.

REST API

Accounts, transactions and entries under /api/v1/. Composite responses — one question, one request.

CLI

The fob-lgr CLI for terminal-first work. Scripts, ad-hoc queries, sync pipelines.

Importers

Idempotent on (source, source_ref). An interrupted sync is safe to re-run — it will not duplicate.

Built for agentsmost differentiated

Every doc page is served as raw markdown at /docs/md/. Point Claude at it and it builds correct requests.

We don't replace your ERP.We make it answerable.

SAP keeps the book of record. Ledger keeps a correct, queryable double-entry copy — and everything downstream reads from the copy instead of another one-off extract.

Reports · BI · Agents · Spreadsheets
Whoever is asking
queries
Ledger
Shadow ledger · double-entry
mirrors
SAP · Statements · your system of record
The book of record

Re-run the sync. Nothing duplicates.

Every write is idempotent on (source, source_ref) and reports back created: true|false. A mirror that silently drifts from its source is worse than no mirror — so identity is load-bearing, not a convenience.

A ledger you can actually ask.