Skip to main content

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.

Asking twice for the same type is not a safe retry

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.

TypeMeans
CASHMoney the user has earned or paid in. The everyday balance
CREDITAn obligation layer — what the user owes you, or a float you extend them
SYSTEMInternal. 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 typeMeans
TRANSFERABLESpendable and movable. Created automatically with the wallet
NON_TRANSFERABLELocked value — a promotion, a bonus with conditions attached

GET /wallets/{walletId}/funds lists them. GET /wallets/{walletId}/balance gives the total.

There is no API to move value between buckets

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 /walletsevery wallet in your tenant, paginated
GET /wallets/{walletId}one wallet
GET /wallets/{walletId}/balancethe total
GET /wallets/{walletId}/fundsper-bucket balances
GET /wallets/summarytenant-level totals

Next: Payment.