Ledger and periods
Money → Ledger is “the platform’s books: accounts and their balances, what is frozen, and whether it all adds up.”
Needs ledger.read. Releasing a hold needs ledger.hold.release.
Everything in the Money section is derived from the ledger, and the ledger is the authority — when a page disagrees with it, the page is wrong.
The ledger is double-entry, and that is load-bearing
Section titled “The ledger is double-entry, and that is load-bearing”Every movement posts a journal, and a journal balances or the write fails. There is no path in the system that records half a movement.
A transaction is collected once and reversed once. The database refuses a
second collection or a second reversal journal for the same transaction, and
the whole posting rolls back (000089). A payout every partner declined is failed
and reversed in one database transaction, so a callback arriving in between can’t
reverse it twice.
That property is what makes the rest of this section meaningful. A balance here is not a counter somebody increments; it is derived from the postings. If a balance looks wrong, the answer is in the journals that produced it, not in the balance.
One journal type moves money onto a merchant’s balance without a collection:
merchant_funding, posted when a merchant deposit
is approved — debit the partner’s float, credit the merchant’s payable.
Amounts are minor units and a currency, everywhere, in the database and in the API. A decimal amount in something that should be reading the ledger is a bug worth reporting rather than a formatting preference.
| Tab | What it is |
|---|---|
| Accounts | Every ledger account and its balances. |
| Holds | Money frozen on an account. |
| Trial balance | Debits against credits, per currency. |
| Closing snapshots | What each period’s figures were at close. |
| Integrity checks | Three checks that should always come back empty. |
Accounts
Section titled “Accounts”| Filter | Values |
|---|---|
| Owner | Platform, Merchant, Partner, External, End user. |
| Account type | Asset, Liability, Revenue, Expense, Equity. |
| Currency | GHS, NGN, XOF, USD. |
| Include closed accounts | Off by default. |
| Column | Notes |
|---|---|
| Account | Code and name. A test-environment account’s code ends in .test; see below. |
| Owner | — |
| Type | — |
| Normal | “Which direction increases this account.” |
| Balance | Everything on the account. |
| Pending | Collected and not yet released to the merchant — on confirmation, after a hold, or on reconciliation, by the merchant’s rule. Not withdrawable. |
| Reserved | “Frozen by open balance holds.” |
| Available | What can actually move. |
| State | Active, Closed, or Closed, non-zero. |
| Last posted | — |
An account’s sheet
Section titled “An account’s sheet”Opens over the list, which keeps its filters and its paging.
| Fact | Notes |
|---|---|
| Code, Name, Currency | — |
| Negative allowed | “An account that forbids it and goes negative is an invariant violation.” |
| Last posted | — |
| Balance row version | “One per posting, for optimistic locking.” |
Plus a Statement — “the last thirty days. The endpoint accepts at most ninety-three, because the window bounds the partitions read.” Widening it is possible; asking for a year is not.
Account lookup
Section titled “Account lookup”The chart is read by account rather than browsed, so the drilldown asks for the account instead of offering a list:
| Field | Notes |
|---|---|
| Owner | Merchant, partner or end user. “Platform is absent on purpose: the lookup matches owner_id by equality and a platform account’s owner_id is NULL.” Filter the list by owner instead. |
| Merchant / Partner / Owner id | Depends on the owner. A merchant is found by search — “by name or code.” A partner is chosen from a dropdown. An end user has no list, so their Owner id is still typed — “copied from the record it belongs to.” |
| Account type | Which of that owner’s accounts. |
| Currency | — |
| Environment | “Test accounts are a separate chart. A live id looked up on test is a miss.” |
An empty result is far more often the wrong environment than a missing account.
“Money frozen on a ledger account. A release needs a second person.”
Placing one takes:
| Field | Notes |
|---|---|
| Category | “The category of hold. The written explanation goes in the reason below.” |
| Release due | “Optional, and it does NOT release the hold. It is the date the hold becomes due for release, after which it appears on the overdue list for a human to act on. Leave it empty for a hold that stands until somebody releases it.” |
| Reason | At least eight characters. “Stored on the hold AND in the audit log — this is what the merchant’s account manager reads when the merchant asks why they have not been paid.” |
The overdue list is paged, 50 at a time, and totalled per currency for the
page shown. From the API (GET /v1/ledger/holds/expired), total_overdue_minor
sums the current page only, across currencies.
Request a hold release raises an approval request:
| Field | Notes |
|---|---|
| Reason | “Why the investigation is over and the money should move again. Every approver reads this.” |
The holds tab is readable by anybody with ledger.read; only the release action
is gated. That is deliberate — somebody has to be able to see why a merchant’s
money is frozen without being able to unfreeze it.
Trial balance
Section titled “Trial balance”Per currency and environment: Balanced, Imbalance, Total debits, Total credits.
An imbalance is not a reporting quirk. It means the books do not add up, and it is the most serious thing this console can tell you.
Integrity checks
Section titled “Integrity checks”Three checks. All three should always come back empty.
| Check | A row here means |
|---|---|
| Integrity — debits against credits | “The double-entry ledger itself does not balance.” Per currency. |
| Balance drift — cached against computed | “Each row is an account whose stored balance is wrong by the drift shown.” |
| Invariants | “Negative available where it is forbidden, reserved not matching the open holds, a drifted flag.” |
Run them before closing a period, and after any incident that touched money movement.
Closing snapshots
Section titled “Closing snapshots”What each period’s figures were at the moment it closed. This is what makes a closed period’s numbers stable: the snapshot is the record, and it does not move when late corrections arrive in a later period. The period picker offers up to 200 periods.
Periods
Section titled “Periods”Money → Periods — “closing seals a month’s figures. Locking seals them permanently, and reopening a reported month needs two people.”
| Column | Notes |
|---|---|
| Period | — |
| Status | Open, Closing, Closed, Locked. |
| Starts | — |
| Ends | “Exclusive. Adjacent periods share this date, so a value date falls in one period only.” |
| Journals | “Control total captured at close. Empty until the period has been closed.” |
| Entries | The same. |
| Closed by | “And the second person who approved it.” |
| Reopened | “Permanent: never reset, which is the whole point of recording it.” |
Three actions, three different controls
Section titled “Three actions, three different controls”This is the page where “approval-gated” is not one thing:
| Action | Control | Takes effect |
|---|---|---|
| Close | Not an approval request. It names a second provider user in the request, and the database refuses it if that is you. | Immediately. |
| Lock | An approval request. Somebody else decides. | When approved. Permanent. |
| Reopen | An approval request. The most serious of the three. | When approved. |
So the close dialog asks you for a colleague rather than telling you to wait. Four eyes, implemented directly.
Readiness, and what forcing means
Section titled “Readiness, and what forcing means”Before a close is offered, the console reads whether the period has ended and how many reconciliation exceptions are unresolved in it.
Reopening
Section titled “Reopening”Changing a month that has already been reported. The reason must be at least ten characters because the database refuses less, and the reopen count is never reset.
If something needs correcting after a close, the usual and correct answer is an adjusting entry in the current period, not a reopened one. Reopen when the reported figures were materially wrong, not to tidy a classification.
Investigating a money question, in order
Section titled “Investigating a money question, in order”- The ledger. What was actually posted? It balances, so it is the truth.
- The settlement batch and its lines. Which transactions were included, and at what net? See Settlements and withdrawals.
- The withdrawal. Did it leave, and did it arrive?
- The merchant’s own records, last. They are the question, not the answer.
Working the other way round — starting from what the merchant believes — is how a reconciliation turns into an argument instead of a finding.

