Settlements
An open entry is a document or payment that has not been fully settled. Settling links entries that cancel each other out and closes them, so each stops counting toward what the counterparty owes.
Three calls do the whole job: list the open entries, preview what settling them
would do, then settle the identical body. Route paths here are relative to the
API mount, so the first is https://api.ledgerbee.com/api/v1/open-entries in
full.
Name exactly one counterparty
Every call takes either customerId or supplierId, never both and never
neither. An entry belongs to one side of the books, so a request carrying both
is refused rather than answered about whichever id was read first.
Read the open entries
GET /v1/open-entries?customerId={id}, scope journal-entries-read — see the
API Reference.
Code
Code
Three fields carry into the settle call: kind, id and version.
remainingAmount is how much of the entry is still open, and the ceiling on
what you may apply to it.
asOf answers as of the end of a given day: ?asOf=2026-08-31 reports the
entries as they stood then, and measures daysOverdue against that date.
Read totals.baseCurrencyOutstanding for the balance, and never sum the
rows. The rows are the individual open documents; the total also includes the
counterparty's opening balance, which no row carries.
Preview the settlement
POST /v1/settlements/preview, scope journal-entries-write — see the
API Reference. Nothing is posted, and the response is a 200.
The preview takes the same body as the settle call, so you send one payload twice. Nothing has to be rebuilt between them, and nothing can differ between what you were shown and what posts.
Code
amountToApply is a magnitude — always positive, and at most that entry's
remainingAmount. Which side of the settlement an entry lands on follows from
what the entry is, so there is no sign to state.
Send the absolute value. A credit note and a payment are reported with a
negative remainingAmount, and passing that figure straight back is refused
with SETTLEMENT_AMOUNT_TO_APPLY_EXCEEDS_REMAINING. Take
Math.abs(remainingAmount). The response signs the amounts for you: an entry
being consumed comes back negative, the one consuming it positive.
Echo each entry's version. It refuses the settlement if the entry was edited
or reversed between reading the list and settling, instead of applying against a
document that has since changed. It does not move when another settlement
applies to the same entry, so an unchanged version is not a promise that as
much is still open — applying more than remains is refused on its own, measured
at the moment of the write.
Read blockers before settling. It carries the codes that would refuse the
settlement, and an empty list is what says the settle call will go through.
warnings are worth checking but do not stop it.
The difference
Settled entries rarely cancel to zero in the company's base currency. The
preview reports what is left over in difference, and the settle call posts it.
difference.kind | What it means | Where it posts |
|---|---|---|
BALANCED | The entries cancel. | Nothing posts. |
FX | They cancel in their own currency but not in the company's, because they were booked at different rates. | The exchange gain/loss account. |
WRITEOFF | A real difference in value. | The debtor-loss account on the customer side, the creditor-loss account on the supplier side. |
difference.account names the account, and postings lists every line that
would be written.
An FX difference on entries booked at rates far apart is usually a mis-booked
rate rather than a real exchange difference. The preview says so in warnings
and names the currencies in rateGap, with the lowest and highest rate any
selected entry in that currency was booked at.
Settle
POST /v1/settlements, scope journal-entries-write — see the
API Reference. Send the body you previewed.
Code
Each settled entry now shows a smaller remainingAmount on the open-entries
listing, or leaves it entirely once fully applied.
Send idempotencyKey to make a retry safe: the same key with the same body
returns the settlement already recorded instead of posting a second one.
Read the settlements back
GET /v1/settlements?customerId={id} lists every settlement recorded against
one counterparty, newest first; GET /v1/settlements/{id} returns one in full,
with the entries it linked and how much of each it applied. Both take scope
journal-entries-read.
Reversed settlements are included in the list rather than hidden, because they
are part of the trail. Check reversed before treating one as still standing: a
reversed settlement contributes nothing, and the entries it linked are open
again and back on the open-entries listing.
An entry's appliedAmount can be smaller than its originalAmount. A payment
is consumed across as many settlements as it takes, and whatever is left stays
open.
Errors
| Condition | What you observe | Fix |
|---|---|---|
| The request names both a customer and a supplier, or neither | 400 SETTLEMENT_COUNTERPARTY_REQUIRED | Name exactly one |
| The counterparty id is not one of yours | 404 CUSTOMER_NOT_FOUND or 404 SUPPLIER_NOT_FOUND | Resolve ids from GET /v1/customers or GET /v1/suppliers |
| Fewer than two entries | 400 SETTLEMENT_INSUFFICIENT_SELECTIONS | A settlement links something owed to something that pays it |
| An entry id is not open, or not this counterparty's | 400 SETTLEMENT_SELECTION_NOT_FOUND | Re-read GET /v1/open-entries and use the ids it reports |
| An entry was settled in full since you read the list | 400 SETTLEMENT_SELECTION_ALREADY_FULLY_CONSUMED | Re-read the listing; the entry is gone from it |
amountToApply exceeds the entry's remainingAmount | 400 SETTLEMENT_AMOUNT_TO_APPLY_EXCEEDS_REMAINING | Apply at most remainingAmount |
amountToApply is zero | 400 SETTLEMENT_AMOUNT_TO_APPLY_ZERO | Apply a positive amount, or drop the entry from the request |
The entry changed since you read its version | 400 SETTLEMENT_OPTIMISTIC_LOCK_VIOLATION | Re-read the listing and settle against the current entry |
| The document behind an entry has been reversed | 400 SETTLEMENT_SELECTION_DOCUMENT_REVERSED | The document is voided and owes nothing |
| The applied amounts pay more than is owed | 400 SETTLEMENT_OVERPAYMENT_REQUIRES_PARTIAL_CONSUME | Apply the amount actually owed and leave the rest of the payment open |
postingDate falls in a closed period or year | 400, one of ACCOUNTING_PERIOD_CLOSED_FOR_POSTING, ACCOUNTING_YEAR_CLOSED, ACCOUNTING_PERIOD_NOT_CONFIGURED, ACCOUNTING_YEAR_NOT_RESOLVED, ACCOUNTING_YEAR_PENDING_RECLOSE | Post to a date in an open period |
| The difference needs an account your company has not configured | 400, one of EXCHANGE_GAIN_LOSS_ACCOUNT_NOT_CONFIGURED, DEBTOR_LOSS_ACCOUNT_NOT_CONFIGURED, CREDITOR_LOSS_ACCOUNT_NOT_CONFIGURED | Assign the account in the gated app under Settings, then retry |
| No debtors/creditors control account covers the entries | 400 SETTLEMENT_CONTROL_ACCOUNT_NOT_CONFIGURED | Assign the control account to the customer or supplier group |
The same idempotencyKey was reused with a different body | 400 SETTLEMENT_IDEMPOTENCY_KEY_CONFLICT | Use a new key for a different settlement |
Reference
Field-level documentation for every endpoint and response shape is in the API Reference.