Skip to content

Reconcile Account

POST
/accounts/{account_id}/reconcile
curl --request POST \
--url https://example.com/accounts/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/reconcile \
--header 'Content-Type: application/json' \
--data '{ "as_of": "2026-04-15", "basis": "posted", "dry_run": false, "statement_balance": 1 }'

Reconcile a checking, savings, cash or credit-card account to a statement balance (issue #268).

Purser compares the statement figure with its own balance on as_of in the chosen basis and posts ONE adjustment for the difference, dated as_of, to the system category expenses.reconciliation_adjustments. The adjustment lands in the review set so a person sees it. When the ledger already matches, nothing is posted and the clean reconcile is still recorded. Either way reconciled_through advances and an account.reconciled event is emitted.

Safe to retry: the same date, balance and basis returns created: false while the ledger still matches, and 409 reconciliation_moved with the difference once a backdated change has moved it. GET /accounts/{id} and GET /accounts/{id}/reconciliations re-check recorded reconciliations the same way on every read.

account_id
required
Account Id
string format: uuid
Media typeapplication/json
ReconcileRequest
object
as_of
required
As Of

The statement date: the balance is the one at the end of this day. Today (UTC) or earlier; a future date is rejected.

string format: date
basis
Basis

Which balance the statement reports: posted (cleared rows only, the usual statement balance) or pending_inclusive (posted plus pending, the ‘current balance’ many banks show).

string
default: posted
Allowed values: posted pending_inclusive
dry_run
Dry Run

Return exactly what the call would do — the same computation, conflicts included — without writing anything.

boolean
statement_balance
required
Any of:
number

Successful Response

Media typeapplication/json
ReconcileResult

What the reconcile did (or, with dry_run, would do). Amounts are in ledger sign except statement_balance, which echoes the input.

object
account_id
required
Account Id
string format: uuid
adjustment
required
Adjustment
string
adjustment_tx_id
required
Any of:
string format: uuid
as_of
required
As Of
string format: date
basis
required
Basis
string
Allowed values: posted pending_inclusive
category
required
Any of:
string
created
required
Created
boolean
dry_run
required
Dry Run
boolean
ledger_balance
required
Ledger Balance
string
reconciled_through
required
Any of:
string format: date
reconciliation_id
required
Any of:
string format: uuid
statement_balance
required
Statement Balance
string
target_balance
required
Target Balance
string

Example

{
"basis": "posted"
}

The request contradicts a recorded reconciliation. detail.code is reconciliation_moved (this exact reconciliation is recorded, but the ledger balance on that date has changed since — detail.difference = ledger minus target), reconciliation_conflict (a different balance or basis is recorded for that date), or reconciliation_out_of_order (the date is before the account’s latest reconciliation).

Validation Error

Media typeapplication/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
ctx
Context
object
input
Input
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string

Example generated

{
"detail": [
{
"ctx": {},
"input": "example",
"loc": [
"example"
],
"msg": "example",
"type": "example"
}
]
}