Description
Ledgers & Balance Management
The CoreBanq Ledger System is a high-performance, double-entry accounting engine. It serves as the "source of truth" for all financial positions, providing real-time balance tracking, hierarchical aggregation, and administrative holds.
Purpose and use
Ledgers record the bank's financial truth: customer money, bank money, fees, suspense, settlement, holds, and control balances. Every balance-affecting operation should be explainable from ledger entries and their roll-up into the chart of accounts.
Who uses this. Finance controllers, operations staff, reconciliation teams, auditors, treasury users, and support teams use ledgers to answer balance, posting, safeguarding, and audit questions. Regulatory reporting teams use the same subledger structure to prepare safeguarding figures, and product teams use it to decide where a new money flow should be booked.
How it works. Customer-facing accounts map to subledgers. Subledgers roll into control ledgers. Ledger entries debit and credit those ledgers, while holds adjust available balance without changing posted balance until settlement occurs.
What users do. Users review ledger hierarchy, inspect balances, place or release holds, post journal entries through controlled flows, and trace a transfer or fee back to its accounting entries.
Outcomes and side effects. Ledger postings update posted and local-currency balances, create audit evidence, and feed account balances, statements, reconciliation, and reporting. Holds affect spendable balance and must be released or settled through traceable operations.
See also:
Object Relationships
The system follows a strict hierarchical and relational model to ensure data integrity and auditability.
Hierarchy Breakdown
- Control Ledgers: Represent categories in the Chart of Accounts (e.g., "Public Customer Deposits"). They aggregate balances from children but never hold individual customer funds directly — a control ledger that has any subledger under it stops accepting direct postings.
- Subledgers: An account or a customer can own several subledgers, one per purpose (Current, Hold, Fees receivable, Interest accrual, Collateral) and currency — see Why one customer has several subledgers below. Every account still gets its Current subledger automatically at opening; the rest open lazily, the first time each is actually needed.
- Customer Account: The user-facing entity. The account balance is a projection of its Current subledger specifically — the account never changes shape when a customer also picks up a Hold or Fees receivable subledger elsewhere.
Monetary Amount Format
All monetary amounts in API responses use the CcyAmtWithPrecision format. This self-describing format expresses amounts in minor/atomic units (e.g., cents for EUR, satoshi for BTC) as a string, alongside the currency code and precision.
{
"amount": "123456",
"currency": "EUR",
"precision": 2
}| Field | Type | Description |
|---|---|---|
amount | string | Integer in minor units (e.g., "123456" = 1234.56 EUR). |
currency | string | ISO 4217 code for fiat or asset code for crypto. |
precision | int | Number of decimal places (e.g., 2 for EUR, 8 for BTC, 0 for JPY). |
To convert to a display value: displayAmount = amount / 10^precision.
This format is used for all balance, amount, and monetary fields including: balance_posted, balance_available, balance_pending, balance_lcy, overdraft_limit, total_amount, amount, amount_lcy, debit, credit, etc.
Balance Management Concepts
CoreBanq distinguishes between different balance states to support complex banking operations like "authorized but not settled" payments and administrative freezes.
Balance Types
| Type | Calculation | Description |
|---|---|---|
| Posted Balance | Sum(Credits) - Sum(Debits) | The settled, legally recognized balance. |
| Pending Balance | Posted + Expected Future Entries | Includes entries that are authorized but not yet value-dated. |
| Available Balance | Posted - Sum(Active Holds) - Pending Debits | The "spendable" amount. This is what the user sees as their current buying power. |
Balance Holds
Holds are administrative or system-driven restrictions on a ledger's available balance. Holds do not decrease the posted balance; they only reduce the spendable amount until they are either released or "settled" (converted into a real debit entry).
Hold Scenarios:
- Card Authorizations: A merchant authorizes $50. A hold of $50 is placed.
- Administrative Hold: Compliance department freezes $10,000 pending verification.
- Overdraft Protection: Negative holds can be used to temporarily increase buying power.
API Reference
Ledger Management (V2)
GET /v1/ledgers
Returns a paginated list of ledgers. By default it lists Control Ledgers only (the Chart of Accounts view); subledgers are included only when explicitly requested.
GET /v1/ledgers/chart
Returns a balance-history time series for charting, not the Chart of Accounts tree. Each series is a list of {x, y} points (x = timestamp, y = balance) at the requested interval. The ledgers to plot are selected with the required search.code.in query parameter (a comma-separated list of ledger codes); an optional color seeds the series colour. Requesting no valid codes returns 400.
GET /v1/ledgers/{id}
Retrieve full details of any ledger (Control or Subledger) including current and available balances.
POST /v1/ledgers
Create a new Control Ledger. Note: Subledgers are managed automatically by the Account service.
Balance Holds (V2)
POST /v2/ledgers/holds
Place a new hold on a ledger. Decreases balance_available immediately.
GET /v2/accounts/{id}/holds
Retrieve all active holds for an account's subledger.
GET /v2/ledgers/holds/{id}
Get detailed information about a specific hold (reason, expiry, notes).
POST /v2/ledgers/holds/{id}/release
Release an active hold. Restores balance_available.
Technical Details
Automatic Subledger Creation
When a new Customer Account is created, the system executes an atomic transaction:
- Creates the Account record.
- Identifies the correct Control Ledger (Parent) based on currency and account type.
- Creates a Subledger linked to that parent.
- Updates the Account to reference this new Subledger.
Routing by customer type
Which control ledger a new account routes to is set directly on the control ledger's own properties — a Customer Types list (Corebanq Configurator → Ledger Browser → open the control ledger → "Customer Types"). A customer type + currency combination routes to exactly one control ledger; the system rejects assigning the same type + currency to a second ledger, so routing can never become ambiguous. This replaced an external configuration file — there is nothing to edit outside the ledger's own properties anymore.
Only a leaf control ledger — one with no child ledgers underneath it — can carry customer types. A parent or group ledger (e.g. the top-level "22" Customer Deposits group, or the "221" Type A Customer Deposits group) exists purely to aggregate its children's balances and can never itself receive an account booking, so it cannot carry a routing either; the Configurator hides the "Customer Types" control for such ledgers, and the API rejects the assignment if attempted directly. Set customer types on the specific currency leaf under the group instead (e.g. "2211" for a Type A customer's CHF deposits, not "221").
On a brand-new environment, the standard chart of accounts already ships with this routing — the public and exempt customer-deposit control ledgers come pre-assigned to their customer types the first time the chart is imported, so a fresh deploy can open accounts immediately without any manual setup.
Subledger description pattern
Subledgers are named automatically. By default, every control ledger produces descriptions like
"Customer Deposits Liabilities - Type A Customer (EUR) - Customer ABC Ltd" — the control ledger's
own description plus the customer's name — with nothing to configure. Admins only need to touch
this when one specific ledger should read differently: open that control ledger's own properties
(Corebanq Configurator → Ledger Browser → "Subledger Description Pattern") and set an override
template, e.g. {parent_description} (Direct Client: {customer_name}). Clearing the field returns
that ledger to the automatic default — there is no need to define the same pattern on every
ledger just to get sensible naming.
Supported tokens: {parent_description} (the control ledger's own description),
{customer_name}, {account_id_short}, {iban_suffix}. If an override references a token with
no value for the account being opened, the legacy "Account {id}" naming is used instead — a
partially rendered description is never posted to the books.
Why one customer has several subledgers
The Current subledger created above is only one of several a customer may end up with. Every franc, euro or dollar the bank holds for a customer lives in a subledger: an individual ledger account that belongs to exactly one customer (or one of their accounts) and rolls up into a bank-level control ledger. Regulation requires that client money be clearly identified in the bank's own accounting, per client — not merely as a pooled wallet total:
- PSD2 Article 10 (safeguarding): client funds must be identifiable in accounting records and never commingled with any other person's funds.
- FCA CASS 7.16.22E (individual client balance method): the firm keeps one balance per client, and may keep one per product or business line per client.
- FCA CASS 15 / PS25/12 (in force 7 May 2026): records must distinguish relevant funds held for each client, support a daily internal reconciliation, a daily external reconciliation against the safeguarding bank, and a D+1 segregation check; a per-customer balance list (resolution pack) must be producible within 48 hours and records retained for 5 years.
A single account balance cannot separate "money the customer can spend" from "fees the customer owes the bank" or "funds reserved under a hold". Each of those is therefore its own subledger, each rolling into its own control ledger, so both the customer view and the bank's financial statements stay precise — and the safeguarding requirement can be computed directly from the ledger.
The structure: one customer, several accounts, several purposes
A customer may hold several accounts in the same currency (for example a main account and a payroll account, both CHF). Every account has its own Current subledger — the one created automatically above. Purposes that concern the relationship rather than a single account — such as fees receivable — are kept once per customer and currency:
The Fees receivable subledger is an asset (what the customer owes the bank), so it must roll into an asset receivables control ledger — never into fee income control 311, which would combine a receivable balance with earned revenue. The current chart does not yet seed such a receivables control, so the roll-up above is shown dashed and no live flow books the fees-receivable purpose until one is added and mapped (see Rollout note).
Reading the picture:
- Account-level purposes (Current, Hold) attach to one account. Two same-currency accounts never share a Current subledger; each account's spendable and held money stays separately traceable.
- Customer-level purposes (Fees receivable, collateral) attach to the customer. What Acme AG owes the bank in fees is one figure regardless of which account eventually pays it.
- Control ledgers hold only summary balances. They are populated exclusively by the roll-up of their subledgers; manual journals against a control ledger are rejected. This mirrors long-standing core-banking practice and structurally prevents the most common cause of subledger-versus-general-ledger reconciliation breaks.
- The sum of all subledgers under a control ledger always equals the control ledger's balance. This invariant is what the daily internal reconciliation verifies.
Codes in these examples. Account codes shown here (e.g.
2211Type A CHF customer deposits,311Type A fee income) come from the standard Audax chart of accounts, grouped by leading digit: Group 1 Assets, Group 2 Liabilities, Group 3 Profit & Loss. The Fees receivable purpose is defined in the system, but the current chart does not yet seed a dedicated fees-receivable control ledger for its subledgers to roll into; until one is added, no live flow books to it (see Rollout note).
When each subledger is opened
Subledgers are opened lazily: only when the first booking of that purpose actually happens. Most customers never need a collateral subledger; none is created for them.
| Moment | What is opened | Anchored to |
|---|---|---|
| Account opening | Current subledger for the account's currency | The account |
| First administrative or card hold on an account | Hold subledger | The account |
| First fee charged to the customer in a currency | Fees receivable subledger | The customer |
| First interest-bearing product on an account | Interest accrual subledger | The account |
| First collateral pledge by the customer | Collateral subledger | The customer |
The customer-facing account is unchanged by any of this: the IBAN, statements and the primary spendable balance continue to work exactly as before. Purpose subledgers are the bank's internal precision, surfaced to operations and reporting.
Booking walkthrough 1 — a monthly fee
Fees are booked in two steps, and the split matters for safeguarding: the moment a fee is charged, that money stops being client money (the requirement drops), even though the cash may briefly remain inside the safeguarding boundary until swept.
Step 1 — Accrual (fee falls due; customer has not yet paid):
| Leg | Subledger | Effect |
|---|---|---|
| Debit | Fees receivable subledger (customer-level) | The bank's claim on Acme AG increases |
| Credit | Fee income 311 (Type A revenues) | Income is recognized |
Step 2 — Settlement (collection from whichever account holds funds):
| Leg | Subledger | Effect |
|---|---|---|
| Debit | Current 2211-a1b2c3d4 (Main account) | Customer's spendable money decreases |
| Credit | Fees receivable subledger | The claim is cleared |
Because the receivable is a real ledger line per customer, the fee queue, dunning and write-off decisions read directly from ledger balances — and the safeguarding calculation can exclude charged-but-unswept fees from the client-money requirement while the external reconciliation still sees the cash, exactly the classification the regulator expects.
If the customer holds several same-currency accounts, settlement may debit any funded account's Current subledger (primary first). The receivable side is unaffected: it is customer-level.
Booking walkthrough 2 — incoming funds and suspense
Safeguarding duties start when funds are received, not when they are allocated. An incoming
payment that cannot be applied immediately (unknown reference, name mismatch) is booked to an
inward suspense account (281 Suspense – IWT – SIC in the standard chart) — a bank-level
internal account, not a customer subledger — so the money is on the books from minute one:
Regulatory anchors for this flow:
- D+1 segregation check (CASS 15 / PSD2 Art. 10): funds received must reach a designated safeguarding account by close of the following business day. Because receipt is booked immediately, the daily D+1 check is a simple aging query over suspense and transit balances, with an audit trail of remediation postings.
- Suspense discipline: suspense accounts are open-item managed — every entry carries a reference and must be reversed by a matching entry, netting to zero per item. Aged-item reports over suspense are a standing operations control; long-outstanding items are the classic early-warning signal auditors look for.
- Individual client balances: the moment allocation happens, the money sits on the specific customer's Current subledger. The per-customer balance list required for the resolution pack is a direct ledger read, reproducible for any past business day from end-of-day balance snapshots.
Daily controls this structure enables
| Control | Frequency | What it checks |
|---|---|---|
| Internal reconciliation | Every business day | Sum of client-money subledgers (requirement) vs sum of safeguarding mirror accounts (resource); shortfalls topped up from own funds, excesses swept out, same day |
| External reconciliation | Every business day | Safeguarding mirror accounts vs the safeguarding bank's records |
| D+1 segregation check | Every business day | No client-attributable transit/suspense balance older than one business day |
| Suspense aging | Every business day | Open suspense items by age, with owner and deadline |
| Control roll-up check | Continuous | Each control ledger equals the sum of its subledgers |
Negative individual balances are never netted against other clients when computing the requirement; they are excluded and surfaced separately as a funding obligation.
Rollout note
Existing accounts already have their Current subledger; nothing changed for them when multi-purpose subledgers went live. Purpose subledgers open lazily, the first time each is actually needed — the first hold, the first payment in transit, the first compliance action — never in advance and never simply because a customer is activated. A customer who is active for years without ever triggering a hold or a compliance action never gets one.
The mechanism described above — several subledgers per account or customer, opened lazily, rolling into their own control ledgers, with a control ledger refusing direct postings the moment it owns any subledger — is live today. No live flow has been moved onto a non-Current purpose yet (no product books to Hold, Fees receivable, Interest accrual or Collateral in production), so day-to-day behaviour is unchanged until a specific flow is migrated onto one. Safeguarding pool classification, end-of-day snapshots and the daily reconciliation views described above arrive in a second phase.
Identification Priority
For all entry and transfer endpoints, you can identify ledgers using:
- UUID (
ledger_id): Most performant, immutable. - Code (
ledger_code): Human-readable (e.g.,2211-abc12345). - Numeric ID (
ledger_num_id): Legacy support.
The system resolves them in the order listed.