Concepts
Simulation
The sandbox settles against simulated money. Nothing real moves, every response says simulated: true, and every outcome a bank can produce is available on demand, so you can build your failure handling before you have a bank.
These triggers mean nothing on a deployment that moves real money. Check GET /api/capabilities first: simulated: true means they apply.
Choose the outcome#
You pick it with the cents of the amount, or with two designated IBANs.
| Trigger | Outcome | Status after execute → after the delay |
|---|---|---|
| any other amount | settled at once | paid |
ends in .01 |
insufficient funds (AM04) | failed |
ends in .02 |
payee account closed (AC04) | failed |
ends in .03 |
no confirmation, then settled | processing (unresolved) → paid |
ends in .04 |
no confirmation, then rejected | processing (unresolved) → failed |
ends in .05 |
accepted, settles later | processing → paid |
ends in .06 |
settled, then returned by the bank (AC01) | paid → returned |
ends in .07 |
rail unavailable, nothing sent | failed |
debtor IBAN NL56SIML0000000001 |
source account frozen, refused before sending | stays approved |
payee IBAN NL29SIML0000000002 |
KYC rejected | kyc_rejected |
| any other payee | KYC approved | approved |
For example, a payout of 20.03 is accepted without confirmation and settles a moment later:
payout = await client.payouts.create(
beneficiary_id=payee.beneficiary_id,
mandate_id=mandate.mandate_id,
amount="20.03",
)
await client.payouts.submit(payout.payout_id)
await client.payouts.wait_for_kyc(payout.payout_id, timeout=30)
execution = await client.payouts.execute(payout.payout_id)
print(execution.status, execution.unresolved) # processing True: do not resend
payout = await client.payouts.wait_for_settlement(payout.payout_id, timeout=60)
print(payout.status) # paidcurl -s -X POST $API/api/payouts -H "$J" -d '{
"beneficiaryId": "'$PAYEE'", "mandateId": "'$MANDATE'", "amount": 20.03, "currency": "EUR"
}' | jq -r .data.payoutId
# submit, wait for approved, then:
curl -s -X POST $API/api/payouts/$PAYOUT/execute | jq -c '.data | {status, unresolved}'
# {"status":"processing","unresolved":true}Timing and balances#
- KYC is decided by the service after a delay, three seconds by default. After
submit, poll the payout until it leavespending_kyc. Do not record the decision yourself. - Every debtor account opens with 1000.00 EUR. A balance below the amount is insufficient funds.
- A
returnedpayout still counts against the mandate's total.failedandkyc_rejecteddo not. - There are no webhooks yet. Poll.
Inspect and reset#
GET /api/simulation returns this table, the delay, and every simulated account with its balance. In Python, await client.simulation.get().
POST /api/reset empties the store and the simulated ledger. The store is shared, so reset only when you mean it. The Python SDK refuses unless the deployment is a simulated sandbox.