Skip to content

Fund your test programme

Create test money in your programme float and put it on cards.

Cards can't spend money that isn't there. This guide covers the three steps between "I have a test programme" and "I have a card that can authorize": put test money into the programme's float, move it onto a card's funding account, and know what happens when you hit the ceiling.

How money flows

Money in test mode moves in one direction, through three stops:

Issuance → programme float → card funding account → spend. A simulated deposit mints test money straight onto the programme's float — there's no external payment rail behind it, so nothing needs to be "cleared" before the balance is usable. From the float, a card top-up moves a slice of it onto one card's own funding account, and only then can that card authorize a simulated spend against it.

Test money is synthetic and mode-isolated: it exists only in test mode, on a programme's test-mode float, and it never touches or offsets anything in live mode. Switching a programme to live mode does not carry a test float's balance with it — live funding is a separate, operator-arranged process.

Deposit to your float

POST /v1/simulated-deposits mints test money onto a programme's float. It requires an Idempotency-Key header — an identical retry replays the stored result, and reusing the key with a different body returns 409:

curl -X POST https://api.rigid.fi/v1/simulated-deposits \
  -H "Authorization: Bearer $RIGID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "program_id": "<programme-id>",
    "amount": { "amount": "250.00", "currency": "GBP" },
    "reference": "Sandbox top-up"
  }'
{
  "id": "0198e2c1-9f3a-7c42-b8d1-6a2f5c0e77b3",
  "program_id": "<programme-id>",
  "amount": { "amount": "250.00", "currency": "GBP" },
  "actor_user_id": "018f2b3c-9f3a-7e5f-8a9b-0c1d2e3f4a5b",
  "transfer_id": "tr_018f2b3c-9f3a-7e5f-8a9b-0c1d2e3f4a5c",
  "float_account_id": "acct_018f2b3c-9f3a-7e5f-8a9b-0c1d2e3f4a5d",
  "reference": "Sandbox top-up",
  "created_at": "2026-08-25T12:00:00Z",
  "float_available": { "amount": "250.00", "currency": "GBP" },
  "replayed": false
}

This route is test mode only, and the caller must be a programme admin for the programme named in program_id. There's nothing to provision ahead of time — the first deposit against a programme lazily creates its float, so a brand-new test-mode programme can go straight from zero to a funded float in one call. reference is optional and echoed back on the record.

To see what's landed so far, list the programme's deposit history. Passing currency also returns that float's current balance as float_available:

curl "https://api.rigid.fi/v1/simulated-deposits?program_id=<programme-id>&currency=GBP" \
  -H "Authorization: Bearer $RIGID_API_TOKEN"

float_available is null when currency is omitted, or when the float has never been minted into. Reference: Simulate a deposit and List simulated deposits.

The console's Sandbox tab carries the same flow as a "Programme float" deposit panel, if you'd rather fund a programme by hand than script it.

Top up a card

A float balance doesn't authorize anything by itself — a card can only spend from its own funding account. POST /v1/cards/{id}/topup moves money from the card's programme float onto that card:

curl -X POST https://api.rigid.fi/v1/cards/<card-id>/topup \
  -H "Authorization: Bearer $RIGID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount": { "amount": "50.00", "currency": "GBP" }
  }'

There's no program_id in the body — the card's own programme and currency are resolved from card_id, and a currency mismatch between the request and the funding account is rejected. If the float doesn't have enough available to cover the top-up, fund it first with a simulated deposit. See the full flow in the quickstart and the reference for Top up a card.

Withdraw and reset

POST /v1/simulated-withdrawals is the deposit's mirror: it burns test money back out of a programme's float rather than minting it in. Same Idempotency-Key requirement, same programme admin requirement for the programme named in program_id. Pass an amount to withdraw a fixed sum:

curl -X POST https://api.rigid.fi/v1/simulated-withdrawals \
  -H "Authorization: Bearer $RIGID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "program_id": "<programme-id>",
    "amount": { "amount": "50.00", "currency": "GBP" }
  }'

Or reset the float in one call: pass drain: true and a currency instead of an amount, and the whole current balance in that currency burns — whatever it happens to be, without you having to look it up first. drain requires currency because the float is per-currency; there's no single balance to drain without saying which one:

curl -X POST https://api.rigid.fi/v1/simulated-withdrawals \
  -H "Authorization: Bearer $RIGID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "program_id": "<programme-id>",
    "drain": true,
    "currency": "GBP"
  }'

A withdrawal can only burn what's still sitting in the float, checked against its posted balance — today identical to the float_available figure the panel reports. Money is not withdrawable once it has left the float: a top-up moves it onto a card's own funding account, and a withdrawal or a drain afterwards can't reach it and won't unwind the top-up. A request for more than the float currently holds fails 400 with code float_insufficient; a withdrawal against a currency the float has never been minted into fails 400 with no_float; and draining a float that's already at zero fails 400 with nothing_to_drain.

Burning is real drawdown, not a cosmetic reset: it restores the same test issuance ceiling headroom the original deposit consumed, so a tenant that's hit its ceiling can withdraw — or drain a float it no longer needs — to buy back room to mint again. Reference: Simulate a withdrawal and List simulated withdrawals.

Both directions are webhook events, if you'd rather react to them than poll: a deposit fires ledger.funds.issued, a withdrawal or drain fires ledger.funds.retired — see the webhooks guide for delivery, verification and retries.

The console's Sandbox tab carries Withdraw and Drain float actions right next to Deposit, on the same programme float panel.

Limits

Simulated deposits draw down a per-tenant test issuance ceiling — a cap on how much test money your tenant can mint in total, across every test-mode programme and float. Once it's exhausted, further deposits fail 400 with code issuance_ceiling_reached. This isn't a per-request throttle you can retry your way past — if you hit it, contact support to have the ceiling raised.

Live mode

Live funding is real money, and it isn't self-serve. It's arranged with your operator during onboarding, outside the API. POST /v1/simulated-deposits reflects that boundary directly: called against a live-mode programme it returns 403 with code live_funding_operator_only, regardless of the caller's role — there is no permission that unlocks it. Test-mode self-serve funding and live-mode operator funding are deliberately two different paths, not one gated behind a role check.