Double-entry accounting for AI agents
Why a running total isn't enough
The naive approach — an agent keeps balance += amount in memory or one column — fails the moment you need to answer "where did this number come from?" There's no audit trail, no way to detect a dropped or double-counted event, and a single bad write corrupts the truth with no way to notice. Double-entry is the centuries-old fix: every change touches at least two accounts and the changes must net to zero. If they don't, the transaction is rejected.
The model, in agent terms
There are five account types. An agent classifies each account it creates as one of them:
| Type | Holds | Example for an agent |
|---|---|---|
| asset | what you have | USDC wallet balance, prepaid API credits |
| liability | what you owe | an unpaid invoice, deferred work owed |
| equity | net worth / starting capital | opening balance funded by the operator |
| revenue | money earned | fees an agent collects for completed tasks |
| expense | money spent | tool/API calls, x402 micropayments, compute |
The sum-to-zero invariant
A transaction is a list of entries; each entry moves an integer amount on one account. The amounts must sum to zero. Earning $50 of revenue into your wallet looks like this — one entry increases an asset, the other records the revenue:
POST /ledger/transactions {"date":"2026-06-16","description":"Fee for completed task", "entries":[ {"account_id":1,"amount":5000}, # Wallet (asset) +$50.00 {"account_id":4,"amount":-5000} # Revenue -$50.00 (credit) ]} # 201 Created {"id":1,"balanced":true,"entries":2}
Amounts are integers in minor units (e.g. cents), so 5000 = $50.00. Try to post entries that don't net to zero and the API returns an error — the invariant is enforced by SQLite triggers, not just application code, so it holds no matter how the row was written.
Immutability and reversals
Real books are append-only: you never delete history, you reverse it. If an agent posts a wrong transaction, it issues a reversing transaction that negates the original; both stay in the ledger with a full trail. This matters for agents because it makes their financial state replayable and auditable after the fact — essential when a human (or another agent) needs to verify what happened.
POST /ledger/transactions/1/reverse {"date":"2026-06-16","description":"Reverse: duplicate fee"} # 201 -> a new transaction mirroring #1 with signs flipped; #1 untouched
What you get for free
- Trial balance — total debits vs credits, always equal if the books are sound.
- General ledger — every entry, filterable by account and date range.
- Per-account balances — as-of any date, derived from entries, never stored and risked.
Ready to wire it up? The quickstart walks through it in five calls, and tracking agent spend and revenue shows a full worked example.
FAQ
Why can't my agent just store a balance?
A single number has no audit trail and no error detection. Double-entry makes the books self-checking (entries must net to zero) and traceable (every balance derives from recorded events), which an agent needs to be verifiable.
What are debits and credits here?
Ledger uses signed integer amounts instead of separate debit/credit columns: a positive amount increases an account, a negative decreases it, and the entries of a transaction must sum to zero. That is equivalent to debits = credits.
What unit are amounts in?
Integers in minor units — e.g. cents, so 5000 means $50.00. Ledger is currency-agnostic; you decide what one unit means and stay consistent.
Can a transaction ever be deleted?
No. Entries are immutable; to undo one you post a reversing transaction. The original remains for the audit trail.
Ledger is live. No signup, no API key — your agent pays per request with USDC on Base.
View the live API →Written and verified by Novadyne, June 2026. Ledger is a production double-entry accounting API at ledger-api.novadyne.ai. Examples are illustrative; the live /.well-known/x402 discovery endpoint is the source of truth for current payment requirements.