https://api.balampay.com/sandbox; in production, drop the /sandbox prefix. Every authenticated call carries both x-api-key and Authorization: Bearer <access token> (see Authentication).
How the sandbox behaves — read this first. Three sandbox behaviors shape every test below:
- Verification is automatic. A user that is complete for a product verifies on its own — usually within seconds — with no verification call for you to make. It can instead route to manual review (
status: REVIEW). - Virtual accounts activate automatically. The sandbox polls provider status every 15 seconds, so a new VA becomes funds-ready in about a minute and fires
virtual_account.activatedon its own. - A simulated deposit credits the VA balance. On the fiat wire rail,
simulate-depositfires the deposit event and webhook and credits the virtual account’s available balance; a payout then debits that same balance, sodeposit → balance → payoutis one connected cycle (GET /v1/virtual-accounts/{id}/balancereflects it, possibly after a few seconds). A VA may also open with a provider-side sandbox float, so a payout can succeed before you simulate a deposit — always confirm the balance covers the amount first. Balance behavior is not uniform across every rail, so treat thevirtual_account.deposit_funds_receivedwebhook as the rail-independent signal. See Known limitations.
1
Authenticate and pin your version
Exchange your credentials for a bearer token, then send The rest of this guide assumes both are exported. Each
X-Api-Version: 2026-04-14 on every request. See the Quickstart for the full credential setup.curl below repeats the two credential headers and the version header.2
Create and verify a user
Create the user with This minimal body returns
POST /v1/users. Send the product-required compliance fields at creation — including expected_monthly_volume and expected_transaction_count — so they are part of the initial verification. Supplying them later, after the user is VERIFIED, re-triggers verification and can temporarily block virtual-account creation (see Known limitations).201, but a user only verifies once it is complete for a product — send the full field set for its category (documents, address, questionnaire) as described in Creating users. Then poll until the user is verified, gating on status (not verification_status, which can contradict it):3
Open a virtual account and watch it activate
Create the virtual account for the verified user with The VA activates on its own. The sandbox polls provider status every 15 seconds, so within about a minute the
POST /v1/virtual-accounts. In the sandbox use bank: "slovak_savings_bank" (in production the same rail is bank: "portage").virtual_account.activated webhook fires and the account becomes funds-ready. Confirm readiness by reading the VA until account_number is a real value — it stays null while an international / crypto VA provisions (on the ACT rail, bank: "austin_capital_trust", it shows the "PENDING-ACT-ACCOUNT" placeholder instead).approved does not mean funds-ready — confirm with account_number and the virtual_account.* webhooks. While the VA is still activating, GET /v1/virtual-accounts/{id}/balance returns 400. If a VA stays in activating well beyond a minute, read its status_reason: a value like User is not eligible for US_BANK: Missing required fields for this product [missing: …] means the user was missing product-required fields at creation (see the previous step). The VA does not self-heal — complete those fields on the user, then create a new VA. If status_reason is empty and it still won’t activate, contact your Kira representative. See Known limitations.4
Simulate a deposit
POST /v1/virtual-accounts/{virtual_account_id}/simulate-deposit has two modes, chosen by the request body: send { amount } (below) to simulate an inbound deposit into an active VA, or { transferUuid } to fund a crypto-funded payout (see the note at the end of this step). It is sandbox-only — the same call returns 403 "Simulate deposit is only available in sandbox environments" in production. The amount mode exercises your deposit-notification, reconciliation, and webhook handling.amount— a positive USD string with up to 2 decimals (more returns400).payment_type— the inbound rail:wire(default) orach.sender_name— optional; defaults to"Simulated Sender".
201 returns the simulated deposit. status is lowercased and depends on the VA mode and amount:The call also fires the
virtual_account.deposit_funds_received webhook. The fees and net_amount in the response mirror what the real settlement path would charge, using your configured fee schedule — they are not fixed numbers.A simulated deposit credits the VA balance. A fiat deposit completes at simulation time (
status: completed, settlement_triggered: false — there is no async settle step to wait for) and, on the fiat wire rail, credits the virtual account’s available balance; a crypto deposit starts pending and the settlement pipeline advances it to completed. Confirm it either through the virtual_account.deposit_funds_received webhook (recommended for event-driven handling) or by the balance delta on GET /v1/virtual-accounts/{id}/balance (which may take a few seconds to reflect). It also appears in GET /v1/virtual-accounts/{id}/deposits. Because a payout debits that same balance, the deposit above and the payout below form one connected deposit → balance → payout cycle. A payout draws on the VA’s whole available balance (any opening sandbox float plus the deposits you credit), and not every rail moves its balance on a simulated deposit — so top the balance up first and keep the webhook as the rail-independent signal. See Known limitations.Second mode — fund a crypto-funded payout with Send exactly one of
{ transferUuid }. A crypto-funded payout (priced with payment_instructions — see Crypto-funded off-ramp) is created CREATED, awaiting an on-chain deposit to its per-payout wallet. Instead of { amount }, send the payout’s transfer id as { "transferUuid": "…" } to simulate that funding deposit so the payout can proceed — no real stablecoin transfer needed.amount or transferUuid — a body with both, or neither, returns 400. Note transferUuid is camelCase, while the amount-mode fields are snake_case. The 201 returns { "transfer_uuid", "status", "job_enqueued": true, "deposit_wallet": { "address", "network", "currency" } }. The transfer must be a crypto-funded payout attached to this VA and still awaiting funding (status: CREATED) with a minted deposit wallet — otherwise 404 "Transfer not found" (also returned for a foreign-client or wrong-VA transfer) or 400 ("Transfer is not a crypto-funded payout", "… missing deposit wallet", or "Transfer is not awaiting funding (status: …)"). Repeat calls are idempotent (a deterministic synthetic txHash), so re-sending won’t double-fund.5
Create a recipient for the payout
A payout’s rail comes from the recipient, so create one with the
account_type you want to pay over. A slovak_savings_bank VA supports WIRE.The recipient response uses
recipient_id (not id) and created_ts (not created_at), and the request account object comes back as a top-level account_type plus account_details. Recipients are immutable and are scoped to your client — a payout may target any recipient in your client. See Payouts, quotations, and recipients and Known limitations.6
Price a quotation
POST /v1/quotations prices the payout. Pass virtual_account_id to price against the VA and persist a redeemable quote_id that locks the fees and FX rate; add from_held_balance: true so the quote can be redeemed by a payout that debits the VA balance.source and recipient money objects, a depeg-aware conversion block, an itemized fees[] array, and totals; the quote expires at quote_expires_at (default 15 minutes). The itemized line-by-line breakdown is returned on the 2026-06-01 pin used above — earlier versions return a simpler preview shape. For a pre-account preview that does not persist, send quote_for: { rail, currency } instead of virtual_account_id (it returns quote_id: null). See Itemized quotations.Quote the flow with the same rail as the recipient you will pay — a quote pins the money (amounts, currencies, network), not the rail, and redeeming it against a different rail can apply a price that no longer matches. See Payouts, quotations, and recipients.
7
Create the outbound payout
First confirm the VA’s balance can cover the source Preview to see the fee breakdown, then create the payout, redeeming the quote by passing
amount. The payout debits the VA’s available sandbox balance — which reflects any simulated deposits you have credited plus any opening sandbox float:quote_id:8
Watch each step complete
Two things complete in this walkthrough. You observe each through status plus webhook — and, on the fiat wire rail, the deposit is also visible as a balance delta on The status walks
GET /v1/virtual-accounts/{id}/balance.The deposit completes at simulation time: a fiat deposit returns status: completed immediately, credits the VA’s available balance, and fires virtual_account.deposit_funds_received. Handle that webhook to reconcile the inbound notification.The payout advances through its state machine on its own in the sandbox. Poll GET /v1/payouts/{id} until it reaches a terminal status, or consume the webhooks:CREATED → PENDING → PROCESSING → COMPLETED (a KYT compliance screen can add a non-terminal KYT_PENDING; a terminal failure surfaces as FAILED). The matching webhooks are payout.created, payout.pending, payout.processing, and payout.completed (plus payout.status_changed on every transition). Completion is not guaranteed instant — treat it like VA activation and drive your flow off the webhook or a poll, not a fixed wait.Casing differs across surfaces — compare case-insensitively.
GET /v1/payouts/{id} returns UPPERCASE status; the flat payout.* webhooks carry lowercase data.status; payout.status_changed carries UPPERCASE at data.data.status. See State machines for the full vocabulary and Webhooks for the event catalog and payload shapes.What completes, and how you observe it
Next steps
Webhooks
The event catalog and payload shapes for the user, virtual-account, deposit, and payout events used above.
State machines
The full status vocabulary and transitions, and how casing differs across surfaces and versions.
Itemized quotations
The full pricing surface of
POST /v1/quotations and how to lock and redeem a quote.Known limitations
Every sandbox and API quirk referenced here, in one place.