1. Create the card
The response tells you what to sign:
2. Sign and submit
SignsignPayload with the wallet owner key via bmoni_embedded_sdk, then submit it:
signPayloadPending came back true, the payload is still being prepared asynchronously. Poll for it first, then sign and submit as above:
A
409 on the sign-payload route means “not ready yet”, not an error — keep polling. Track the issuance itself with GET /v1/users/{userId}/smart-wallets/proposals/{proposalId}.migrationRequired: true instead of a proposal, the wallet is a legacy (pre-Safe) wallet that cannot hold cards. Managed wallets migrate automatically and the create is retried in the same request; when the sweep is still confirming, follow migrationHint — sign the attached payload if present, otherwise retry card creation shortly.
Read cards
Cards are listed and read on the smart-wallet path, not the user path.
A card carries
status, currency, type (physical or virtual), fundingPolicy (SAFE, PERSONAL, or BOTH), fundLifecycle (ONE_TIME or REPEATABLE), and fundedAt — null until the card is first funded. The detail route adds balanceMinor and the most recent 100 ledger entries, newest first.
Cards that have been requested but not yet issued — the issuance proposal is still awaiting signatures, or the card (typically a physical one) is still being provisioned — appear in the same cards list as reserved cards, exactly like the personal card list: status is RESERVED and isReserved is true. A reserved entry has no card id yet, so its id is the issuance proposal id (also exposed as proposalId, matching the create response), alongside the requested cardName, cardColor, currency, and type, plus proposalStatus (PENDING_APPROVALS, PENDING_SIGNATURES, READY_TO_EXECUTE, EXECUTING, or COMPLETED while provisioning finishes). If an entry sits at PENDING_SIGNATURES, the sign-and-submit step from the create flow is still outstanding.
Once issued, the reserved entry is replaced by the real card. A physical card then reports status: PENDING — it stays PENDING through delivery until the holder activates it, so the pre-activation card is always visible in the list: first as RESERVED, then as PENDING. A reserved entry can also disappear without producing a card — when the proposal expires unsigned or is rejected — so treat a disappearance without a matching new card as a dead request, and check the proposal via GET /v1/users/{userId}/smart-wallets/proposals/{proposalId} if in doubt.
The /sensitive route returns only the provider card identity. For the full card number, CVV, expiry, and billing address:
Activate a physical card
Two calls. Virtual cards need neither.channel is required (sms or email); pan is optional when the number is already known upstream. The response returns otpId and expiresInMinutes.
code is exactly 6 digits.
Manage a live card
A PIN is always exactly 4 digits.
GET …/limits returns the current totalDailyLimit and maxSingleTransactionAmount alongside availableDailyLimit and the provider’s caps (maxTotalDailyLimit, maxSingleTransactionLimitCap) — read it before setting a limit the provider will reject.
GET …/transactions accepts size, status (e.g. COMPLETED), and from / to as ISO 8601 dates.
Card statuses
A card’s status is set from two different places, and conflating them is the usual cause of a card that appears stuck.What you can set
PUT /v1/users/{userId}/cards/{cardId}/status accepts exactly two values:
Anything else is rejected with
400 before the request leaves BMONI. The values are case-sensitive and upper-case.
What a card can report
Beyond the two you can set, a card carries statuses that originate with the card issuer and that you can only read:Read these case-insensitively and treat the set as open. They are the issuer’s vocabulary rather than BMONI’s, and an issuer can introduce a state without a change on our side. Branch on the ones you handle and fall through to a neutral “unavailable” state for anything else, rather than asserting an exhaustive match.
Permitted transitions are enforced, not published
There is no published transition matrix. The issuer validates each transition at request time against the card’s current state, and rejects the ones it does not allow with:Inspecting a card
POST …/cards/{cardId}/reset resets the PIN, not the status.
Related
- Transfers — the proposal → approve → sign flow in full
- Integration flow — every endpoint in call order
- NGN deposits & withdrawals — funding the wallet a card spends from

