Skip to main content

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:
  • OnrampPOST /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.
EnvironmentAcceptedRejected
Sandboxtestnets (ethereum-sepolia, polygon-amoy, arbitrum-sepolia, base-sepolia, optimism-sepolia, solana-devnet), evmmainnets
Productionmainnets (ethereum-mainnet, polygon-mainnet, arbitrum-mainnet, base-mainnet, optimism-mainnet, solana-mainnet), evmtestnets
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:
  • typeach_inbound or wire_inbound for USD rails.
  • account_id — required for ach_inbound and wire_inbound. Use the onramp account ID.
  • scenariosuccess_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:
typeEffect
kyb_approveFully approves the customer (any type). Triggers endorsement, recipient creation, and customer.kyb_status.created webhooks.
kyb_rejectSets application status to declined.
kyb_info_requestSets application status to request_for_information.
kyc_approve, kyc_reject, kyc_info_requestUpdates the individual applicant’s KYC status only. Does not trigger the full onboarding flow.
applicant_activateSame effect as kyb_approve. Does not auto-create accounts, wallets, or account numbers — create those separately after activation.
applicant_suspendSets 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.

Available Headers

HeaderTypeEffect
X-Sandbox-Error-StatusintegerHTTP status code returned at the configured error step.
X-Sandbox-Error-Messagestring (≤ 500 chars)Error message body returned at the configured error step.
X-Sandbox-Error-Stepstring (≤ 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-Scenariostring enumApplies a preset failure mode. See the Scenarios table below.
X-Sandbox-Instant-CompletionbooleanWhen true, async simulation flows complete immediately rather than requiring a manual advance call. Default false.
X-Sandbox-Skip-Auto-ApprovalbooleanWhen 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.
ScenarioBehavior
happy_pathEverything succeeds immediately.
delayed_settlementSucceeds but pauses for manual progression via POST /sandbox/simulations/{id}/advance.
insufficient_fundsReturns 400 at transaction_processing with “Source account has insufficient funds for this transaction”.
compliance_blockReturns 403 at compliance_check with “Transaction blocked: compliance review required. Contact support.”
invalid_accountReturns 400 at account_validation with “Invalid destination account or address”.
provider_maintenanceReturns 503 at provider_call with “Provider temporarily unavailable for maintenance. Please try again later.”
network_congestionReturns 429 at transaction_processing with “Network congested, please retry later”.
kyb_manual_reviewKYB stays pending at kyb_approval; no auto-approval fires.
kyb_rejectedReturns 403 at kyb_submission with “KYB verification failed: Unable to verify business information”.
kyb_expiredReturns 400 at kyb_submission with “KYB session expired. Please start a new verification.”
network_timeoutReturns 504 at network_call with “Request timed out. Please try again.”
intermittent_errorsReturns 500 at provider_call roughly 30% of the time with “Temporary error occurred. Please retry.”
account_frozenReturns 403 at provider_call with “Account is frozen pending review”.
document_expiredReturns 400 at kyb_submission with “Document verification expired, please resubmit”.
invalid_swiftReturns 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 patternClassification
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 0xMANUAL000000000Same as review.
Anything elseRiskLevelLow, 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

  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 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:
  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
    DAKOTA_API_KEY=your_production_api_key
    DAKOTA_BASE_URL=https://api.platform.dakota.xyz
    DAKOTA_PUBLIC_KEY=your_production_webhook_public_key
    
  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. Roll out gradually — start with low-value transactions, monitor error rates, and have a rollback plan.

Next Steps

API Reference