---
title: "Test accounts in sandbox"
description: "Simulate a deposit on a sandbox v2 account with POST /v2/accounts/simulate/deposit, and know what sandbox cannot do yet."
---
**API version:** v2.

[`POST /v2/accounts/simulate/deposit`](/api-reference/accounts-v2/post-v2-accounts-simulate-deposit) puts a test deposit on a sandbox account. The deposit runs through the real process, including fees, ledger entries and webhooks.

**A money example.** You simulate a 250.00 USD wire into Ada's sandbox account. Because her account is wallet-backed, test USDC first reaches the account's wallet, and then Gravv records the bank deposit. You receive `transfer.inflow.deposit_settled`, and `ledger_balance.projected` rises first, then `ledger_balance.posted`.

## Before you start

- A sandbox key (it starts with `grvSec_sandbox_`). If you use a scoped API key, it needs `transfer:initiate`.
- An `active` sandbox account with bank details.

## What sandbox can and cannot do

- **Sandbox KYC does not complete today.** A sandbox customer stays `pending`, so every `POST /v2/accounts` in sandbox returns `CUSTOMER_KYC_INCOMPLETE` until Gravv fixes this. Capabilities, requirements, the lists and the error paths work.
- Gravv simulates NGN accounts, and they open at once. The other options call their banks' own test systems, so activation is not instant and can be blocked. You cannot force a block.
- Sandbox can list different options, limits and excluded countries than live.

## Simulate a deposit

Each new `Idempotency-Key` makes a new deposit. The same key returns the same `reference`.

```bash title="Simulate a wire deposit"
curl --request POST \
     --url https://api.gravv.xyz/v2/accounts/simulate/deposit \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 53b44e53-fd48-4ab9-9791-d29de1325df4' \
     --header 'Content-Type: application/json' \
     --data '
{
  "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
  "amount": "250.00",
  "rail": "wire"
}
'
```

```json title="Response"
{
  "data": {
    "accepted": true,
    "reference": "d3b07384-d9a0-4c8f-9e2b-6f1a2c3d4e5f",
    "state": "pending"
  },
  "error": null
}
```

`accepted` does not mean Gravv has credited the deposit. Wait for the [deposit webhooks](/platform/accounts/receive-deposits).

| Account | `rail` |
| :-- | :-- |
| USD | `ach`, `wire`, `rtp` or `swift`, from the option's `rails` |
| EUR | `sepa` |
| GBP | `fps` |
| NGN, KES | Leave `rail` out. For NGN, send `"currency": "NGN"` and an amount in naira such as `"50000.00"` |

## Errors

| Response | Why | Fix |
| :-- | :-- | :-- |
| 403 `Forbidden: insufficient permissions to perform this action` | A live key | Use a `grvSec_sandbox_` key |
| 403 with `required_any` `transfer:initiate` | A scoped key without the permission | Add it |
| 400 `invalid request payload: cannot unmarshal number …` | `amount` sent as a number | Send `"250.00"` |
| 400 `rail must be one of ach, wire, rtp, swift, sepa, fps` | Unknown rail | Use a listed rail |
| 400 `account has no linked virtual account to deposit into` | The account is not `active` yet | Wait for activation |
| 400 `account has no wallet to deliver a simulated deposit to` | A wallet-backed account without a wallet | Open a new account on another network |
| 400 `amount cannot be expressed on this account's receiving rail` | More than 2 decimals | Use at most 2 decimals |
| 400 `the simulated deposit was not recorded; the amount may be below what this account's rail credits` | Too small | Use a larger amount |
| 400 `deposit simulation is not supported for this account's receiving rail` | No simulator for this option | Test with another option |
| 404 `Resource not found` | The `account_id` is wrong, is a live id or belongs to another business. The API looks up the account before it checks the amount | Use a sandbox account id |

For test funds in an account's wallet, see [Fund account wallets during testing](/platform/accounts/fund-account-wallets-during-testing).
