Panel — Reports
- Base path
/api/panel/v1/wallet- Identity
- Panel session (JWT) —
Authorization: Bearer <panel access token>. The token comes from panel login; an s2s signing key does NOT open these endpoints. - Authorization
- RBAC permission:
wallet:report:read·wallet:fee:manage·wallet:terminal:read·wallet:bulk:create - Cross-surface
- The endpoints on this page have NO equivalent in the s2s API; you cannot call them from your own panel. These capabilities exist only in the merchant panel, behind a panel session.
GET reports/overview # overview (programId, date range)
GET reports/operations # operation volume and breakdown
GET reports/fees # fee and tax revenue
GET reports/campaigns # campaign impact and budget consumption
GET reports/terminals # per-terminal activity
GET reports/custody # custody — total customer money heldYou can also export from listing endpoints: operations/export, bulk-payout-statements/period.
Custody report
GET reports/custody # programId, asOfShows the total customer money held in the program. It is the most critical report for compliance and reconciliation: this figure must line up with the balance in your backing account.
asOf — the report is a MOMENT, not a period
A custody report is a stock, not a flow. A stock has no period, it has an instant — which is why the endpoint accepts asOf (ISO8601) rather than from/to. For flow within a period use reports/operations.
Leaving asOf empty returns the live/cumulative position (the default behaviour).
The blocked columns cannot be carried as-of. Amounts blocked by cash-out codes and card authorizations cannot be rewound to a past instant, and the report does not hide this — it tells you, through blockedBasis:
CURRENT— noasOfwas given; everything is a snapshot of the same instant.CURRENT_NOT_AS_OF—asOfwas given, but the blocked columns are CURRENT values.
When reconciling a past date and you see CURRENT_NOT_AS_OF, do not treat the blocked columns as that date's data; explaining a reconciliation gap with them will give you the wrong answer.
Check the gap between custody and the backing balance regularly. Any difference should be explainable by suspense funds, outflows awaiting reconciliation (AWAITING_RECON) or held operations (HELD). An unexplained gap must be investigated.
Fee report
GET reports/feesFee and tax breakdown. When a figure surprises you:
- Look at the operation's
scheduleVersionId. - Check which threshold branch was taken in that version's matrix (
appliedThreshold,appliedTimeKey). - Check when that schedule version became active.
Seeing "two different fees for the same operation type" is normal when a schedule version changed; if you are comparing across a version boundary, split by scheduleVersionId.
Campaign report
GET reports/campaigns
GET campaigns/:id/reportHow many operations benefited and how much budget was spent. If a campaign is paused (budget protection) the curve flattens — do not mistake that for falling demand.
Operations report
GET reports/operationsVolume by type, status and period. For reconciliation:
| Watch | Why |
|---|---|
AWAITING_RECON count | A rising count means something is wrong on the bank side |
FAILED rate | A sudden jump points to a limit/gate misconfiguration |
HELD count | Risk rules may be too tight |
EXPIRED rate | Users are not completing the confirmation step — likely a UI problem |
Track these four regularly. Most wallet problems show up in these curves before any user complains.
Terminal report
GET reports/terminalsFilterable by store code, terminal status and date range. It is only useful if terminal fields were filled in consistently — see Panel — QR & Terminals.
Reconciliation discipline
- Compare custody daily. The gap must be explainable.
- Keep the
AWAITING_RECONqueue empty. A growing queue means an unresolved problem on the bank side. - Match fee revenue to schedule versions. Comparisons across version boundaries mislead.
- Watch campaign budgets. A paused campaign is silent but visible in reports.
- Export with narrow date ranges. Wide ranges are slow and hard to analyse.