Transactions and entries
Updated 10 September 20263 min read
On this page
A transaction changes balances. It is a set of entries, each a debit or a credit against one account, and the set has to balance. This is the rule that keeps a ledger trustworthy: money is never created or lost, only moved.
The balancing rule
Within a transaction, the sum of debit amounts must equal the sum of credit amounts in each currency. If it does not, the request fails with HTTP 422 and the code entries_unbalanced.
A transaction must contain at least 2 entries and at most 100.
{
"ledger_id": "led_01J9QK3ZV8",
"description": "Invoice INV-2041 settled",
"status": "posted",
"effective_at": "2026-09-10T00:00:00Z",
"entries": [
{ "account_id": "acc_01J9QK5M2A", "direction": "debit", "amount": 480000, "currency": "USD" },
{ "account_id": "acc_01J9QK5N7C", "direction": "credit", "amount": 450000, "currency": "USD" },
{ "account_id": "acc_01J9QK5P1D", "direction": "credit", "amount": 30000, "currency": "USD" }
]
}
Amounts are positive integers in minor units. The direction says which way the entry goes.
Pending and posted
| Status | Meaning |
|---|---|
pending |
The entries are recorded and reserve funds, but are not final. |
posted |
The entries are final and permanent. |
archived |
A pending transaction that was voided. It never affected posted balances. |
Create a transaction as pending when you need to hold funds before you know the outcome, for example while a payout is being reviewed. Then:
POST /v1/transactions/{id}/postmakes it final.POST /v1/transactions/{id}/voidreleases the hold and archives it.
A pending transaction affects the pending and available balances immediately, but not the posted balance. See Account balances.
Posted entries are immutable
Once a transaction is posted, nothing about it can change, not the amounts, the accounts or the description. There is no update and no delete. This is deliberate: an auditor can trust that history was not rewritten.
To fix a mistake, reverse the transaction:
curl -X POST https://api.ledgerline.dev/v1/transactions/txn_01JA5C9T/reverse \
-H "Authorization: Bearer $LEDGERLINE_API_KEY" \
-H "Idempotency-Key: 2c8e6d1a-8f40-47a4-9d6e-0d5f3a1b7c92" \
-d description="Reversal: duplicate invoice"
A reversal creates a new posted transaction with the opposite entries and links it to the original through reverses. The original keeps its place in history and gains reversed_by. Reversing emits a transaction.reversed event. A transaction can be reversed once.
Effective date and backdating
Each transaction has an effective_at timestamp, which is when the movement counts for reporting. It defaults to now. You may set it up to 90 days in the past. Anything older fails with period_closed, because statements for that period have already been issued. Future dates are allowed for pending transactions only.
Balance protection
Posting is checked against each account's available balance. If a credit-normal account has allow_overdraft set to false, which is the default, and an entry would push its available balance below zero, the whole transaction is rejected with insufficient_funds. No partial transaction is ever written.
Metadata and references
Attach up to 50 key-value pairs of metadata to a transaction, with keys up to 40 characters and values up to 500. Use external_id to store your own identifier. It must be unique per ledger, which gives you a natural idempotency check on top of the Idempotency-Key header described in Idempotency.
Batches
When you import many transactions at once, use POST /v1/transactions/batch. It accepts up to 500 transactions per request and processes them asynchronously. The response is HTTP 202 with a batch id, and each transaction succeeds or fails independently. The endpoint allows 30 requests per minute per key. See Rate limits.
Events
| Event | Fired when |
|---|---|
transaction.posted |
A transaction becomes posted, including directly at creation. |
transaction.reversed |
A reversal transaction is posted. |
Subscribe through webhook endpoints.