Sandbox Philosophy
Dakota’s sandbox runs real crypto custody on testnets and mocks the banking layer. Wallets sign and broadcast real testnet transactions; the policy engine runs unmodified. Fiat rails (ACH, Fedwire) are mocked. What that means per flow:- Onramp —
POST /sandbox/simulate/inboundmocks the USD deposit. The follow-on stablecoin transfer to the Destination is a real testnet transaction. - Offramp — send real testnet crypto to the offramp’s
source_crypto_address. The on-chain inbound is real; the USD outbound to the bank Destination is mocked. - Swap — fully on-chain on testnets, end to end.
POST /sandbox/simulate/onboarding instead of waiting for real compliance review.
- Base URL:
https://api.platform.sandbox.dakota.xyz - Dashboard:
https://platform.sandbox.dakota.xyz - API keys: Base64-encoded strings, generated from the dashboard.
Sandbox Restrictions
Two safeguards run on every object-create request in sandbox to prevent accidental mainnet activity and unbounded test spend.Network Restriction
Object-create endpoints —POST /customers/{id}/recipients, POST /recipients/{id}/destinations, POST /accounts, and POST /transactions — reject mainnet networks in sandbox and reject testnet networks in production. The evm wildcard is allowed in every environment.
A request that violates the rule returns
400 Bad Request. Use the testnet equivalent for the chain you’re targeting.
Amount Cap
Per-transaction amounts in sandbox are capped at $2 USD per request. The cap applies to USD, USDC, and RD (all treated 1:1 to USD). USDT is not supported in sandbox at the moment — it will be enabled in a near-future release. Production has no cap and supports the full asset set. The cap applies at:POST /transactions— theamountfield. Rejected synchronously with400 Bad Request.POST /sandbox/simulate/inbound— theamountfield. Rejected synchronously with400 Bad Request.POST /sandbox/wallets/{wallet_id}/faucet— theamountfield. Rejected synchronously with422 Unprocessable Entity.- Auto-account inbound deposits — when a sandbox crypto deposit lands on an auto-account’s source address and the converted USD value exceeds the cap, the auto-account transaction is created and then transitioned to
failedwith afailure_reasonindicating the cap was exceeded. The failure surfaces via the standardtransaction.auto.updatedwebhook (and onGET /auto-transactions/{id}); there is no synchronous rejection because the deposit has already settled on-chain.
"1.00", "2.00") in your sandbox test fixtures.
FX Access
Foreign-currency (FX) SWIFT payouts and FX quotes need FX access. In sandbox, FX access is enabled by default for every client, including fixed (rate-locked) quotes, so you can test FX payouts without asking for it. A client created in sandbox gets FX access within about a minute of creation; until then, FX payouts are rejected with400 FX access is not enabled and the FX currency list is empty.
Sandbox FX covers outgoing payouts only: a one-off (POST /transactions) or offramp that pays a SWIFT destination in a foreign currency. Foreign-currency onramps (an account with a non-USD source_asset, for example EUR in by SWIFT) are not available in sandbox and are rejected.
In production, FX is off by default and is enabled per client on request. Fixed quotes are not generally available in production yet and are also enabled on request. Contact Dakota support before you go live with FX.
Sandbox FX uses live market rates (from fastFOREX) with a 50 basis point (0.50%) spread, the same 17 payout currencies as production (AUD, CAD, CHF, CZK, DKK, EUR, GBP, HKD, HUF, JPY, MXN, NOK, NZD, PLN, SEK, SGD, ZAR), and payouts progress to completion automatically. Sandbox FX adds no FX-specific transaction limits. A foreign-currency payout’s amount is not compared with the amount cap; the deposit that funds it is capped at $2 USD-equivalent instead, so each FX payout is worth at most about $2 in its own currency. CNY is not offered.
Use GET /customers/{customer_id}/fx-currencies to see which currencies a customer can use. A currency that is not in that list (for example THB, PHP, INR, KRW or IDR) is rejected even when FX access is enabled.
Simulation Endpoints
Simulate an Inbound Payment
POST /sandbox/simulate/inbound injects an inbound USD payment event (ACH or Fedwire) against an onramp account, mocking what would normally arrive from a real bank wire. The call is accepted synchronously; webhook callbacks fire asynchronously and match the production event shape exactly. Crypto deposits aren’t simulated — send real testnet crypto to the offramp/swap/one-off source_crypto_address directly. To give a Dakota wallet a testnet balance, use the faucet.
type—ach_inboundorwire_inboundfor USD rails.account_id— required forach_inboundandwire_inbound. Use the onramp account ID.scenario—success_immediate(default) orsuccess_delayedwith adelay_secondsfield.simulation_id— your idempotency key for the simulation. Repeating the call with the same ID and identical params returns the original response; conflicting params return409.
Simulate an Onboarding Transition
POST /sandbox/simulate/onboarding drives KYB or KYC application status through a sandbox transition without waiting for real compliance review.
type values:
Full schema: Simulate an Onboarding State Transition.
To test your RFI handling, call
kyb_info_request, then poll GET /customers/{customer_id} or GET /applications/{application_id} instead of waiting for a webhook.
No type value simulates a Proof of Address (PoA) review or rejection. The sandbox also has no way to trigger the $3,000 rolling 7-day PoA threshold, because the amount cap limits each simulated payment to $2.00. See Proof of Address for individual customers for the production behavior.
Fund a Wallet from the Faucet
POST /sandbox/wallets/{wallet_id}/faucet sends testnet RD to a Dakota wallet on Base Sepolia. It is a real on-chain transfer, so the funds arrive through the same deposit path, and with the same webhooks, as any other deposit. Use it to give a sandbox wallet a balance to test with, for example before simulating a card purchase.
202 Accepted with a simulation_id. Poll GET /sandbox/simulations/{simulation_id} until state_position is completed (see Tracking Simulation State). If it reads failed, the faucet failed: call it again with a new idempotency key.
asset—RD, the only asset the faucet sends today. Optional.amount— up to the $2 amount cap per call. Call the faucet again for more.- The wallet must be an EVM wallet, because RD is sent on Base Sepolia.
Full schema: Fund a sandbox wallet from the testnet faucet.
Tracking Simulation State
Stateful scenarios (e.g.compliance_hold, manual_review, unconfirmed) pause partway through the lifecycle and wait for an explicit advance call:
GET /sandbox/simulations/{simulation_id}— returns current state and the callback delivery history.POST /sandbox/simulations/{simulation_id}/advance— unpauses with actionrelease,reject,approve, etc. (valid actions vary per scenario).
Recovering a stuck sandbox customer
A customer created withPOST /customers automatically gets an onboarding application — check for application_id on the customer record. If a customer has no application_id and can’t be driven through onboarding, remove it with DELETE /customers/{customer_id} and recreate it over the API. The delete is a soft-delete and is blocked once the customer has any accounts or is referenced as a sub-client.
Sandbox Behavior Control Headers
Sandbox accepts a small set of HTTP request headers that override the default success path on a per-request basis. They let you exercise error branches and edge cases without contriving the upstream conditions. These headers are sandbox-only — they have no effect againstapi.platform.dakota.xyz. They are also intentionally not part of the OpenAPI spec; this section is the canonical reference.
Available Headers
X-Sandbox-Error-Status, X-Sandbox-Error-Message, and X-Sandbox-Error-Step work together — set all three to inject a custom error at a specific pipeline step. X-Sandbox-Scenario is the higher-level alternative: pick a named scenario and the platform fills in step, status, and message for you.
Scenarios
Valid values for theX-Sandbox-Scenario header. The same list is also available programmatically via GET /sandbox/scenarios.
TRM Compliance Mock Patterns
Sandbox runs a mock TRM Labs compliance client that returns deterministic risk classifications based on patterns in the address being screened. This lets you exercise high-risk and manual-review code paths without sourcing real flagged wallets.compliance_block scenario (set via X-Sandbox-Scenario) overrides the address-pattern logic and forces a RiskLevelCritical blocked response on every screening call — useful when you want to test the blocked-transaction branch without crafting a specific address.
Webhook Testing
Sandbox webhooks fire from the same delivery pipeline as production — same envelope, same signing key derivation, same retry behaviour. UseX-Dakota-Event-ID for idempotency and verify signatures the same way you will in production. See Webhooks for the full reference.
ngrok for Local Development
Webhook.site for Quick Testing
- Visit
https://webhook.siteand copy the unique URL. - Register it as a webhook target.
- Trigger a simulation; observe the live deliveries.
Required Headers
Rate Limit Testing
Sandbox enforces the same rate limits as production. Send a burst of read requests to verify your client’s backoff:429 responses once the per-key limit is hit.
Common Issues
HTTP 400 on object create with a *-mainnet network
The sandbox network restriction rejects mainnet network IDs. Use the testnet equivalent (ethereum-sepolia, base-sepolia, etc.) or evm for chain-agnostic destinations.
HTTP 400 on a one-off transaction or simulate inbound with amount > 2.00
The sandbox amount cap rejects per-request amounts above $2 USD. Cap your test fixtures at "1.00" or "2.00".
HTTP 400 FX access is not enabled
In sandbox, FX access is enabled for every client shortly after the client is created. Retry after a minute. If the error persists, FX access was switched off for your client; contact Dakota support. In production, FX access must be requested. See FX Access.
HTTP 400 on POST requests in general
X-Idempotency-Key is required on every POST request. Generate a fresh UUID per logical request.
Webhooks not received
Confirm your endpoint is publicly reachable, returns2xx within 30 seconds, and has been registered with POST /webhooks/targets. ngrok or webhook.site are the easiest local test loops.
Transactions stuck in processing
UsePOST /sandbox/simulate/inbound to push the lifecycle forward. For paused stateful scenarios, use POST /sandbox/simulations/{id}/advance.
Migration to Production
When you’re ready to switch:-
Update configuration — change the base URL to
https://api.platform.dakota.xyzand replace sandbox API keys with production keys. Update webhook URLs to your production receiver. -
Environment variables
-
Switch network IDs — replace testnet IDs (
*-sepolia,*-amoy,*-devnet) with their mainnet counterparts. Production rejects testnet IDs. - Remove cap-imposed amount limits — the $2 cap is sandbox-only. Use real transaction amounts in production.
- Request FX access if you pay out in foreign currencies. FX access, and fixed quotes, are enabled for every client in sandbox but per client on request in production. See FX Access.
- Roll out gradually — start with low-value transactions, monitor error rates, and have a rollback plan.
Next Steps
- Authentication — production security practices.
- Customer Onboarding — production KYB flows.
- Common Flows — onramp, offramp, swap, and wallet flows end-to-end.
- Webhooks — webhook signature verification and event reference.

