Panel — Programs
- 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:program:read·wallet:program:manage·wallet:limit:manage·wallet:sponsor-policy:manage·wallet:sponsor-policy:approveFour-eyes: the requester and the approver cannot be the same person — this is enforced by identity comparison, not by a role gate. - 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.
A program is the definition of your wallet product. Every user, account and operation under it is governed by its policies.
Creating and editing
GET programs # list
POST programs # create
GET programs/:id # detail
PATCH programs/:id # edit| Field | Description |
|---|---|
name | Program name |
loopMode | Closed-loop / open-loop mode |
currencies | Supported currencies |
railPolicy | Integration policy |
branding | Branding fields — see Portal — Branding & Theme |
status | Status |
licenceHolder | Licence-holder axis |
Use expectedVersion when editing. If two operators edit the same program at once, the request without the expected version is rejected instead of silently overwriting.
Program status and archiving
From the merchant panel, status accepts only ACTIVE and SUSPENDED.
ARCHIVED cannot be set from the merchant panel. Archiving a program is a platform-management operation. Use SUSPENDED to take a program out of service; if it genuinely has to be archived, contact the platform team.
An archive request passes two gates and both are fail-closed. You need to understand what they report, because you are the one who has to drain the queues:
| Error code | What it says | What you must do |
|---|---|---|
WALLET_PROGRAM_ARCHIVE_BLOCKED_BALANCE | The program still has accounts holding a balance | Settle the balances (refund or pay out to the user) |
WALLET_PROGRAM_ARCHIVE_BLOCKED_IN_FLIGHT | Even at zero balance, there is money in flight | Close the relevant queue (axis table below) |
WALLET_PROGRAM_ARCHIVE_UNVERIFIABLE | The program is above the inline balance-verification cap | The platform drain procedure is required; balances are verified out of band |
Do not render these two codes with the same message. ..._BALANCE means "settle the balances"; ..._IN_FLIGHT means "close the queue". They are different jobs, and a single "the program cannot be archived" string sends the operator to the wrong one. The balance gate runs first.
Money in flight — the axis breakdown
WALLET_PROGRAM_ARCHIVE_BLOCKED_IN_FLIGHT reports structurally which queue holds how many records:
{
"code": "WALLET_PROGRAM_ARCHIVE_BLOCKED_IN_FLIGHT",
"details": {
"inFlightByAxis": { "OPERATION": 12, "CASHOUT_CODE": 500 },
"capped": true,
"probeCap": 500,
"axes": [
{ "axis": "OPERATION", "count": 12, "capped": false, "remedy": "…" },
{ "axis": "CASHOUT_CODE", "count": 500, "capped": true, "remedy": "…" }
]
}
}| Axis | What blocks | Remedy |
|---|---|---|
OPERATION | An operation saga that has not reached a terminal state | Let the sagas finish, or reverse them |
CASHOUT_CODE | Outstanding cash-out codes | Release them or let them expire |
CARD_AUTHORIZATION | Outstanding card authorizations | Clear or reverse them |
BULK_PAYOUT_BATCH | Outstanding bulk payout batches | Cancel or finish them |
Counting is capped at 500 (probeCap). When an axis reports capped: true, the real figure is not 500 but "500+" — never render it as an exact number.
The capping flag is per axis (axes[].capped). Do not infer which axis was capped from the top-level capped boolean: an axis holding exactly 500 records that was not capped would be shown as "500+" because a different axis overflowed. axes[].capped exists precisely to remove that guess.
The reason the balance gate cannot see money in flight is technical and important: the balance read looks at the customer wallet account, while blocked funds have been moved into a separate blocked bucket that read never touches. And a saga that has not posted yet appears in neither bucket. That is why the second axis exists.
OTP policy
GET programs/:id/otp-policy
PUT programs/:id/otp-policyDefines when the program asks for an OTP. There are two rules:
| Field | Meaning |
|---|---|
otpAmountThreshold | OTP is required for transactions above this amount |
newCounterpartyOtp | When the user pays a business or recipient for the first time, OTP is required regardless of the amount: ON or OFF |
newCounterpartyOtp can be turned off only on a closed-loop program; on an open-loop program the rule always applies and an OFF request is rejected. If you omit the field the stored value does not change. The default is ON.
In the response, newCounterpartyOtpConfigurable shows whether the rule can be turned off on this program, and newCounterpartyOtpEffective shows whether it currently applies.
Setting the threshold to 0 means "no threshold", not "OTP on everything". If newCounterpartyOtp is also OFF, the program never asks for an OTP. After saving, verify the behaviour with one small and one large test transaction.
If otpAmountThreshold is omitted, the program-specific threshold is removed and the platform default applies. When you only change newCounterpartyOtp, send the current threshold as well.
Assigning a limit scheme
POST programs/:id/limit-schemeBinds a limit scheme to the program. Scheme contents: Panel — Limit & Fee Schemes.
Cross-channel shared cap
GET programs/:id/shared-limit-caps
PUT programs/:id/shared-limit-caps/:capCode/tiersDefines the umbrella cap shared by selected outbound channels. It is off by default; agree the thresholds with your compliance team before enabling. See Limits.
Binding bank integrations
GET programs/:id/bank-rails
PUT programs/:id/bank-rails/:railId
DELETE programs/:id/bank-rails/:railIdThe binding screen is built; what is needed is a bank integration to bind. Without an integration, bank transfer top-up, bank withdrawal, IBAN transfer and IBAN-targeted bulk payouts do not work. Status: Roadmap.
A program can run with several bank integrations at once; direction, status and credential reference are configured per integration.
Sponsor policy
GET programs/:id/sponsor-policy
PUT programs/:id/sponsor-policy
POST programs/:id/sponsor-policy/validate
POST programs/:id/sponsor-policy/versions/:versionId/submit
POST programs/:id/sponsor-policy/versions/:versionId/approve
POST programs/:id/sponsor-policy/versions/:versionId/reject
GET sponsor-policy/providersThe sponsor policy declares which licence and providers the program runs on: e-money provider, enabled integrations, ATM provider, crypto provider and assets, card issuer and BIN sponsor.
The policy is validated before approval: a policy naming a provider that is not connected will not be approved. Run validate before saving — it tells you exactly which binding is missing.
The policy is versioned and subject to four-eyes approval.
Provider switching
GET programs/:id/provider-switches
POST programs/:id/provider-switches
GET programs/:id/provider-switches/:switchId/readiness
POST programs/:id/provider-switches/:switchId/drained
POST programs/:id/provider-switches/:switchId/abortMoving from one provider to another requires an explicit switch record; you cannot simply edit the provider field.
A switch has three stages: open the switch record → drain in-flight operations on the old provider (drained) → complete. The readiness endpoint shows which stage you are at and what is still pending. If it goes wrong, abort rolls it back.
Agents
GET agents
POST agentsAgent (dealer) definition: float account, daily credit limit and KYC reference. See Top-up → Agent top-up.
Things to watch
| Topic | Why it matters |
|---|---|
| Adding a currency | Users must open new accounts; existing accounts are not converted |
| Changing integration policy | Can affect in-flight operations; do it in a low-volume window |
| Program status | Deactivating stops users from transacting |
| Branding fields | They also appear on receipts and notifications |