Documentation Index
Fetch the complete documentation index at: https://docs.dakota.xyz/llms.txt
Use this file to discover all available pages before exploring further.
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/one-off — reject mainnet networks in sandbox and reject testnet networks in production. The evm wildcard is allowed in every environment.
| Environment | Accepted | Rejected |
|---|
| Sandbox | testnets (ethereum-sepolia, polygon-amoy, arbitrum-sepolia, base-sepolia, optimism-sepolia, solana-devnet), evm | mainnets |
| Production | mainnets (ethereum-mainnet, polygon-mainnet, arbitrum-mainnet, base-mainnet, optimism-mainnet, solana-mainnet), evm | testnets |
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 DKUSD (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/one-off — the amount field. Rejected synchronously with 400 Bad Request.
POST /sandbox/simulate/inbound — the amount field. Rejected synchronously with 400 Bad Request.
- 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.
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.
# Inbound ACH against an onramp account
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/simulate/inbound \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "sim_onramp_001",
"type": "ach_inbound",
"account_id": "2LfZn6LNoSvMGuSQ0pLLxj6BLGR",
"amount": "1.00",
"currency": "USD",
"scenario": "success_immediate"
}'
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.
# Approve a customer's KYB
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/simulate/onboarding \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "kyb_approve",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_approve_001"
}'
# Reject KYB with a reason code
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/simulate/onboarding \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "kyb_reject",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_reject_001",
"reason_code": "MISSING_EIN"
}'
# Request additional information
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/simulate/onboarding \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "kyb_info_request",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_info_001",
"info_request_fields": ["ssn", "address"]
}'
type values:
type | Effect |
|---|
kyb_approve | Fully approves the customer (any type). Triggers endorsement, recipient creation, and customer.kyb_status.created webhooks. |
kyb_reject | Sets application status to declined. |
kyb_info_request | Sets application status to request_for_information. |
kyc_approve, kyc_reject, kyc_info_request | Updates the individual applicant’s KYC status only. Does not trigger the full onboarding flow. |
applicant_activate | Same effect as kyb_approve. Does not auto-create accounts, wallets, or account numbers — create those separately after activation. |
applicant_suspend | Sets application status to declined. |
Full schema: Simulate an Onboarding State Transition.
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).
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.
| Header | Type | Effect |
|---|
X-Sandbox-Error-Status | integer | HTTP status code returned at the configured error step. |
X-Sandbox-Error-Message | string (≤ 500 chars) | Error message body returned at the configured error step. |
X-Sandbox-Error-Step | string (≤ 100 chars) | Pipeline step at which the injected error fires. Valid values: transaction_processing, compliance_check, account_validation, provider_call, kyb_submission, kyb_approval, network_call. |
X-Sandbox-Scenario | string enum | Applies a preset failure mode. See the Scenarios table below. |
X-Sandbox-Instant-Completion | boolean | When true, async simulation flows complete immediately rather than requiring a manual advance call. Default false. |
X-Sandbox-Skip-Auto-Approval | boolean | When true, suppresses the 5-second KYB auto-approval. Use to test pending KYB states. Default false. |
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.
# Apply a preset scenario and skip the manual advance step
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "X-Sandbox-Scenario: insufficient_funds" \
-H "X-Sandbox-Instant-Completion: true" \
-H "Content-Type: application/json" \
-d '{ ... }'
# Inject a bespoke 503 at the provider_call step
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "X-Sandbox-Error-Status: 503" \
-H "X-Sandbox-Error-Message: Provider degraded" \
-H "X-Sandbox-Error-Step: provider_call" \
-H "Content-Type: application/json" \
-d '{ ... }'
Scenarios
Valid values for the X-Sandbox-Scenario header. The same list is also available programmatically via GET /sandbox/scenarios.
| Scenario | Behavior |
|---|
happy_path | Everything succeeds immediately. |
delayed_settlement | Succeeds but pauses for manual progression via POST /sandbox/simulations/{id}/advance. |
insufficient_funds | Returns 400 at transaction_processing with “Source account has insufficient funds for this transaction”. |
compliance_block | Returns 403 at compliance_check with “Transaction blocked: compliance review required. Contact support.” |
invalid_account | Returns 400 at account_validation with “Invalid destination account or address”. |
provider_maintenance | Returns 503 at provider_call with “Provider temporarily unavailable for maintenance. Please try again later.” |
network_congestion | Returns 429 at transaction_processing with “Network congested, please retry later”. |
kyb_manual_review | KYB stays pending at kyb_approval; no auto-approval fires. |
kyb_rejected | Returns 403 at kyb_submission with “KYB verification failed: Unable to verify business information”. |
kyb_expired | Returns 400 at kyb_submission with “KYB session expired. Please start a new verification.” |
network_timeout | Returns 504 at network_call with “Request timed out. Please try again.” |
intermittent_errors | Returns 500 at provider_call roughly 30% of the time with “Temporary error occurred. Please retry.” |
account_frozen | Returns 403 at provider_call with “Account is frozen pending review”. |
document_expired | Returns 400 at kyb_submission with “Document verification expired, please resubmit”. |
invalid_swift | Returns 400 at account_validation with “Invalid SWIFT code provided”. |
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.
| Address pattern | Classification |
|---|
Contains hack (case-insensitive) | RiskLevelHigh, score 75, categories [KNOWN_SCAMMER, BLACKLISTED_ADDRESS], is_blocked: false, should_alert: true. |
Contains scam (case-insensitive) | Same as hack. |
EVM address starting 0xBAD0000… | Same as hack. |
EVM address starting 0xSCAM0000… | Same as hack. |
Bitcoin address starting bc1qBADBADB… | Same as hack. |
Contains review (case-insensitive) | RiskLevelMedium, score 50, categories [UNUSUAL_ACTIVITY, REQUIRES_REVIEW], not blocked, not alerted. |
Contains manual (case-insensitive) | Same as review. |
Exact address 0xMANUAL000000000 | Same as review. |
| Anything else | RiskLevelLow, score 10, no categories, not blocked, not alerted. |
# Create a destination using a high-risk-classified address
curl -X POST https://api.platform.sandbox.dakota.xyz/recipients/$RECIPIENT_ID/destinations \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"network": "ethereum-sepolia",
"address": "0xBAD0000000000000000000000000000000000000"
}'
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
# Expose your local server
ngrok http 3000
# Register the HTTPS tunnel as a webhook target
curl -X POST https://api.platform.sandbox.dakota.xyz/webhooks/targets \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url": "https://abc123.ngrok.io/webhooks", "active": true}'
Webhook.site for Quick Testing
- Visit
https://webhook.site and copy the unique URL.
- Register it as a webhook target.
- Trigger a simulation; observe the live deliveries.
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 within the dedupe window returns the original response.curl -X POST https://api.platform.sandbox.dakota.xyz/customers \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{...}'
Rate Limit Testing
Sandbox enforces the same rate limits as production. Send a burst of read requests to verify your client’s backoff:
for i in {1..150}; do
curl -s -o /dev/null -w "%{http_code}\n" \
https://api.platform.sandbox.dakota.xyz/customers \
-H "X-API-Key: $DAKOTA_API_KEY" &
done
wait
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 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:
-
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.
-
Environment variables
DAKOTA_API_KEY=your_production_api_key
DAKOTA_BASE_URL=https://api.platform.dakota.xyz
DAKOTA_PUBLIC_KEY=your_production_webhook_public_key
-
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.
-
Roll out gradually — start with low-value transactions, monitor error rates, and have a rollback plan.
Next Steps
API Reference