Open the wallet
Usually you do not have to. Registering a user opens the wallet types your tenant is configured
to provision automatically, which for most tenants means CASH. By the time signup returns, the
everyday wallet already exists — check the response before you call anything here.
You need this endpoint for a type that is not auto-provisioned, typically CREDIT:
POST /wallets
Idempotency-Key: <uuid>
{ "userId": "8c1f…", "walletType": "CREDIT", "currency": "MYR" }
Currency is MYR. There is one.
A user holds at most one wallet per type, enforced in the database. A second request for a type they
already hold does not replay and does not return the existing wallet — it fails the constraint and
surfaces as a 500. The idempotency key does not save you here, because the two calls are not the
same call. Read what the user already has instead of asking again.
Wallet types
A wallet type is a semantic separation, not a technical one — both are ordinary double-entry wallets. Which types you may open is configured per tenant when we onboard you.
| Type | Means |
|---|---|
CASH | Money the user has earned or paid in. The everyday balance |
CREDIT | An obligation layer — what the user owes you, or a float you extend them |
SYSTEM | Internal. The counterparty for single-sided postings. Not callable by you |
A user holds at most one wallet per enabled type.
Fund buckets
Every wallet's balance is held in one or more funds, not as a single number.
| Fund type | Means |
|---|---|
TRANSFERABLE | Spendable and movable. Created automatically with the wallet |
NON_TRANSFERABLE | Locked value — a promotion, a bonus with conditions attached |
GET /wallets/{walletId}/funds lists them. GET /wallets/{walletId}/balance gives the total.
An endpoint for converting NON_TRANSFERABLE to TRANSFERABLE existed but was never callable —
it was gated on a role no token can carry — and was removed rather than left in the surface
pretending to work.
If you need bucket conversion, or a reserved/held balance, tell us. It comes back through the ordinary route: a feature flag, a scope, and a policy rule. Note that a reservation also needs an expiry and a sweeper, neither of which exists today — without them, held value strands.
Freeze
PUT /wallets/{walletId}/freeze
PUT /wallets/{walletId}/unfreeze
A frozen wallet refuses postings with 422. Use it when you suspect fraud on an account; it stops
money moving without destroying history.
Reading
GET /wallets | every wallet in your tenant, paginated |
GET /wallets/{walletId} | one wallet |
GET /wallets/{walletId}/balance | the total |
GET /wallets/{walletId}/funds | per-bucket balances |
GET /wallets/summary | tenant-level totals |
Next: Payment.