Skip to main content
This guide covers how to test your Dakota Platform integration in our sandbox environment.

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/inbound mocks 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.
Onboarding lifecycle (KYB / KYC) is driven by 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 — the amount field. Rejected synchronously with 400 Bad Request.
  • POST /sandbox/simulate/inbound — the amount field. Rejected synchronously with 400 Bad Request.
  • POST /sandbox/wallets/{wallet_id}/faucet — the amount field. Rejected synchronously with 422 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 failed with a failure_reason indicating the cap was exceeded. The failure surfaces via the standard transaction.auto.updated webhook (and on GET /auto-transactions/{id}); there is no synchronous rejection because the deposit has already settled on-chain.
Use small values ("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 with 400 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.
Key fields:
  • type — ach_inbound or wire_inbound for USD rails.
  • account_id — required for ach_inbound and wire_inbound. Use the onramp account ID.
  • scenario — success_immediate (default) or success_delayed with a delay_seconds field.
  • simulation_id — your idempotency key for the simulation. Repeating the call with the same ID and identical params returns the original response; conflicting params return 409.
The full schema and the catalog of available scenarios live in the API reference: Simulate an Inbound Payment Event, List Simulation Scenarios.
Sandbox only — production returns 403 Forbidden.

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.
Sandbox only — production returns 403 Forbidden.

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.
The call returns 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.
Sandbox only — production returns 403 Forbidden.

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 action release, reject, approve, etc. (valid actions vary per scenario).

Recovering a stuck sandbox customer

A customer created with POST /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 against api.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 the X-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.
The 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. Use X-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

  1. Visit https://webhook.site and copy the unique URL.
  2. Register it as a webhook target.
  3. Trigger a simulation; observe the live deliveries.

Required Headers

X-Idempotency-Key is required for ALL POST endpoints.Per the OpenAPI spec, every POST request must include an X-Idempotency-Key header — a unique UUID per logical request. Requests without it fail with 400. Repeating the same key and body within the dedupe window (24 hours) returns the original response; the same key with a different JSON body returns 422 (idempotency-key-reused). A few routes are not cached (for example POST /fx/quotes and the x402 signature and settle routes), and non-JSON bodies such as multipart uploads are not compared, so a reused key on those is not rejected.

Rate Limit Testing

Sandbox enforces the same rate limits as production. Send a burst of read requests to verify your client’s backoff:
Expect 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, returns 2xx 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

Use POST /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:
  1. Update configuration — change the base URL to https://api.platform.dakota.xyz and replace sandbox API keys with production keys. Update webhook URLs to your production receiver.
  2. Environment variables
  3. Switch network IDs — replace testnet IDs (*-sepolia, *-amoy, *-devnet) with their mainnet counterparts. Production rejects testnet IDs.
  4. Remove cap-imposed amount limits — the $2 cap is sandbox-only. Use real transaction amounts in production.
  5. 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.
  6. Roll out gradually — start with low-value transactions, monitor error rates, and have a rollback plan.

Next Steps

API Reference