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

Sandbox Environment

The Dakota Platform sandbox provides a fully-featured testing environment that mirrors production behavior with mocked external services:
  • Base URL: https://api.platform.sandbox.dakota.xyz
  • Dashboard: https://platform.sandbox.dakota.xyz
  • API Keys: Base64-encoded strings (e.g., AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=)

Getting Started

1. Create Sandbox Account

  1. Ask Dakota Support for a Sandbox account login
  2. Go to the Dashboard: https://platform.sandbox.dakota.xyz
  3. Generate test API keys
  4. Note the different behavior of sandbox vs production

2. Sandbox Limitations

  • No real money or crypto involved
  • Simulated payment processing
  • Limited external provider integrations
  • Faster processing times for testing

3. Key Differences: Sandbox vs Production

FeatureProductionSandbox
External API callsReal third-party providersMocked responses
Money movementReal fundsSimulated
KYB verificationReal verificationAuto-approved after 5 seconds
Compliance checksReal TRM screeningConfigurable mock responses
WebhooksFrom external providersSimulated via /sandbox/* endpoints
Bank accountsReal routing/account numbersMock data (routing: 123456789)
Crypto addressesReal wallet addressesRandom Ethereum-style addresses
Processing timesMinutes to hoursSeconds (configurable)

Sandbox Behavior Control Headers

The sandbox provides HTTP headers to control behavior, inject errors, and simulate specific scenarios.

Available Headers

HeaderTypeDescription
X-Sandbox-Instant-CompletionbooleanComplete async operations immediately instead of requiring manual simulation
X-Sandbox-Skip-Auto-ApprovalbooleanPrevent automatic KYB approval (default: KYB auto-approves after 5 seconds)
X-Sandbox-ScenariostringUse a predefined test scenario
X-Sandbox-Error-StatusintegerInject a specific HTTP error status code (100-599)
X-Sandbox-Error-MessagestringCustom error message (max 500 characters)
X-Sandbox-Error-StepstringSpecify at which step the error should occur

Using Instant Completion

By default, sandbox transactions require manual progression through states via the simulation endpoints. Use this header to complete operations immediately:
cURL
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "X-Sandbox-Instant-Completion: true" \
  -H "Content-Type: application/json" \
  -d '{...}'

Predefined Scenarios

The following scenarios are validated and accepted by the sandbox middleware:
ScenarioDescriptionEffect
compliance_blockTransaction blocked by complianceReturns 403 error at compliance_check step
insufficient_fundsSource account has insufficient fundsReturns 400 error at transaction_processing step
slow_processingDelayed processingSimulates slower processing times
document_requiredAdditional documentation neededTriggers document requirement flow
cURL
# Example: Test compliance block
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "X-Sandbox-Scenario: compliance_block" \
  -H "Content-Type: application/json" \
  -d '{...}'

Custom Error Injection

For scenarios not in the predefined list, use direct error injection headers:
Use CaseHeaders to Use
KYB rejectionX-Sandbox-Error-Status: 403 + X-Sandbox-Error-Step: kyb_submission
Provider maintenanceX-Sandbox-Error-Status: 503 + X-Sandbox-Error-Step: provider_call
Network timeoutX-Sandbox-Error-Status: 504 + X-Sandbox-Error-Step: network_call
Rate limitingX-Sandbox-Error-Status: 429
Invalid accountX-Sandbox-Error-Status: 400 + X-Sandbox-Error-Step: account_validation

Valid Error Steps

StepDescription
account_validationDuring account validation
compliance_checkDuring compliance screening
kyb_submissionDuring KYB submission
transaction_processingDuring transaction processing
provider_callWhen calling external providers
network_callFor network operations
cURL
# Example: Inject custom error at specific step
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "X-Sandbox-Error-Status: 503" \
  -H "X-Sandbox-Error-Message: Provider temporarily unavailable" \
  -H "X-Sandbox-Error-Step: provider_call" \
  -H "Content-Type: application/json" \
  -d '{...}'

Sandbox Simulation Endpoints

The sandbox provides dedicated endpoints to simulate external events that would normally come from provider webhooks.

Simulate Account Activity

Endpoint: POST /sandbox/accounts/{account_id}/simulate Simulate deposits (onramp) or drains (offramp) on accounts. This endpoint creates webhooks that are processed by the orchestrator to create transactions.

Request Body

FieldTypeRequiredDescription
amountstringYesAmount to simulate (e.g., "100.00")
currencystringYesCurrency code - must match the account’s input asset
referencestringNoOptional reference for the transaction
target_statestringNoTarget state for simulation

Currency Validation

The currency must match the account’s input_asset:
  • Onramp accounts (USD → USDC): use currency: "USD"
  • Offramp accounts (USDC → USD): use currency: "USDC"

Target States

For Onramp (Fiat → Crypto):
Target StateTransaction StatusDescription
funds_receivedpendingFunds received, awaiting processing
payment_submittedprocessingPayment submitted to provider
payment_processedcompletedPayment fully processed
For Offramp (Crypto → Fiat):
Target StateTransaction StatusDescription
createdpendingDrain initiated
pendingprocessingDrain in progress
completedcompletedDrain completed
cURL
# Simulate $100 USD deposit to onramp account
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/accounts/{account_id}/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100.00",
    "currency": "USD",
    "target_state": "payment_processed"
  }'
JavaScript
const response = await fetch(
  `https://api.platform.sandbox.dakota.xyz/sandbox/accounts/${accountId}/simulate`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      amount: '100.00',
      currency: 'USD',
      target_state: 'payment_processed'
    })
  }
);

Simulate KYB Status

Endpoint: POST /sandbox/customers/{customer_id}/kyb/simulate Manually update KYB verification status.

Request Body

FieldTypeRequiredDescription
statusstringYesNew status: approved, rejected, in_review, or expired
reasonstringNoOptional reason for the status change
cURL
# Approve KYB
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/customers/{customer_id}/kyb/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "approved"}'

# Reject KYB with reason
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/customers/{customer_id}/kyb/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "rejected",
    "reason": "Unable to verify business documentation"
  }'
By default, KYB auto-approves after 5 seconds in sandbox mode. Use X-Sandbox-Skip-Auto-Approval: true header when creating KYB to prevent this and test pending states.

Simulate Transaction State

Endpoint: POST /sandbox/transactions/{transaction_id}/simulate Manually progress a transaction to a new state.

Request Body

FieldTypeRequiredDescription
statusstringYesNew status: pending, processing, completed, or failed
error_messagestringConditionalError message (required if status is failed)
cURL
# Complete a transaction
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/transactions/{transaction_id}/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'

# Fail a transaction
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/transactions/{transaction_id}/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "failed",
    "error_message": "Insufficient funds"
  }'

Simulate Blockchain Event

Endpoint: POST /sandbox/blockchain/simulate Simulate blockchain events like transfers or confirmations.
cURL
curl -X POST https://api.platform.sandbox.dakota.xyz/sandbox/blockchain/simulate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "transfer",
    "from": "0x1234...",
    "to": "0xabcd...",
    "amount": "100.00",
    "asset": "USDC",
    "network": "ethereum-mainnet"
  }'

Compliance Testing

The sandbox includes a mock TRM compliance client. Use specific address patterns to trigger different risk levels:
Pattern in AddressRisk LevelRisk Score
Contains BADHigh75
Contains SCAMHigh75
Contains hackHigh75
Contains reviewMedium (Manual Review)50
Contains manualMedium (Manual Review)50
Normal addressesLow10
Using X-Sandbox-Scenario: compliance_block triggers a critical risk level (score: 95) that blocks the transaction.

Mock Data in Sandbox

The sandbox generates realistic mock data:
EntityMock Values
Routing Number123456789 (constant)
Account NumberRandom 10-digit number
Bank NameDakota Ridge Bank
IBANRandom German IBAN (DE format)
IBAN BankDakota Europa Bank
Crypto AddressRandom Ethereum-style (0x + 40 hex chars)
TX HashRandom (0x + 64 hex chars)

Testing KYB Onboarding

Sandbox KYB Behavior

In sandbox, KYB verification is simulated:
// Create test customer - application is automatically created
const customer = await dakota.customers.create({
  customer_type: "business",
  name: "Test Business"
});

// The response includes application_url for onboarding
console.log('Onboarding URL:', customer.application_url);

// In sandbox, KYB auto-approves after 5 seconds
// Or use the simulate endpoint to manually control status

KYB Test Scenarios

Control KYB outcomes using specific email patterns:
Email PatternKYB Result
approved@test.comAuto-approved
rejected@test.comAuto-rejected
pending@test.comStays pending
review@test.comRequires manual review

Testing Transactions

Transaction Test Cases

Successful Onramp

cURL
# Successful onramp transaction
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
  -H "X-API-Key: AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
    "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
    "source_asset": "USD",
    "source_network_id": "fiat",
    "destination_asset": "USDC",
    "destination_network_id": "ethereum-mainnet",
    "amount": {
      "value": "100.00",
      "asset": "USD"
    }
  }'
JavaScript
// Successful onramp transaction
const transaction = await fetch('https://api.platform.sandbox.dakota.xyz/transactions/one-off', {
  method: 'POST',
  headers: {
    'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
    'X-Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    customer_id: '31TgvwFzi3rstV0DEDzQtuBfwFR',
    destination_id: '31TgvySz1ARnqMZUdbuxykqqxGV',
    source_asset: 'USD',
    source_network_id: 'fiat',
    destination_asset: 'USDC',
    destination_network_id: 'ethereum-mainnet',
    amount: {
      value: '100.00',
      asset: 'USD'
    }
  })
});
Python
# Successful onramp transaction
transaction_response = requests.post(
    'https://api.platform.sandbox.dakota.xyz/transactions/one-off',
    headers={
        'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
        'X-Idempotency-Key': str(uuid.uuid4()),
        'Content-Type': 'application/json'
    },
    json={
        'customer_id': '31TgvwFzi3rstV0DEDzQtuBfwFR',
        'destination_id': '31TgvySz1ARnqMZUdbuxykqqxGV',
        'source_asset': 'USD',
        'source_network_id': 'fiat',
        'destination_asset': 'USDC',
        'destination_network_id': 'ethereum-mainnet',
        'amount': {
            'value': '100.00',
            'asset': 'USD'
        }
    }
)

Failed Transaction (Insufficient Funds)

cURL
# Transaction that will fail due to large amount
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
  -H "X-API-Key: AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
    "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
    "source_asset": "USD",
    "source_network_id": "fiat",
    "destination_asset": "USDC",
    "destination_network_id": "ethereum-mainnet",
    "amount": {
      "value": "999999.00",
      "asset": "USD"
    }
  }'
JavaScript
// Transaction that will fail due to large amount
const transaction = await fetch('https://api.platform.sandbox.dakota.xyz/transactions/one-off', {
  method: 'POST',
  headers: {
    'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
    'X-Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    customer_id: '31TgvwFzi3rstV0DEDzQtuBfwFR',
    destination_id: '31TgvySz1ARnqMZUdbuxykqqxGV',
    source_asset: 'USD',
    source_network_id: 'fiat',
    destination_asset: 'USDC',
    destination_network_id: 'ethereum-mainnet',
    amount: {
      value: '999999.00', // Will fail
      asset: 'USD'
    }
  })
});
Python
# Transaction that will fail due to large amount
transaction_response = requests.post(
    'https://api.platform.sandbox.dakota.xyz/transactions/one-off',
    headers={
        'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
        'X-Idempotency-Key': str(uuid.uuid4()),
        'Content-Type': 'application/json'
    },
    json={
        'customer_id': '31TgvwFzi3rstV0DEDzQtuBfwFR',
        'destination_id': '31TgvySz1ARnqMZUdbuxykqqxGV',
        'source_asset': 'USD',
        'source_network_id': 'fiat',
        'destination_asset': 'USDC',
        'destination_network_id': 'ethereum-mainnet',
        'amount': {
            'value': '999999.00',  # Will fail
            'asset': 'USD'
        }
    }
)
Go
package main

import (
    "bytes"
    "net/http"
    "github.com/google/uuid"
)

func main() {
    client := &http.Client{}
    body := bytes.NewBufferString(`{
        "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
        "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
        "source_asset": "USD",
        "source_network_id": "fiat",
        "destination_asset": "USDC",
        "destination_network_id": "ethereum-mainnet",
        "amount": {
            "value": "999999.00",
            "asset": "USD"
        }
    }`)
    
    req, _ := http.NewRequest("POST", "https://api.platform.sandbox.dakota.xyz/transactions/one-off", body)
    
    req.Header.Add("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
    req.Header.Add("X-Idempotency-Key", uuid.New().String())
    req.Header.Add("Content-Type", "application/json")
    
    resp, _ := client.Do(req)
    defer resp.Body.Close()
}
Rust
use reqwest::header::{HeaderMap, HeaderValue};
use uuid::Uuid;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    
    let mut headers = HeaderMap::new();
    headers.insert("X-API-Key", HeaderValue::from_static("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc="));
    headers.insert("X-Idempotency-Key", HeaderValue::from_str(&Uuid::new_v4().to_string())?);
    headers.insert("Content-Type", HeaderValue::from_static("application/json"));
    
    let body = json!({
        "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
        "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
        "source_asset": "USD",
        "source_network_id": "fiat",
        "destination_asset": "USDC",
        "destination_network_id": "ethereum-mainnet",
        "amount": {
            "value": "999999.00",
            "asset": "USD"
        }
    });
    
    let response = client
        .post("https://api.platform.sandbox.dakota.xyz/transactions/one-off")
        .headers(headers)
        .json(&body)
        .send()
        .await?;
    
    Ok(())
}
Java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.UUID;

public class DakotaFailedTransactionExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        String body = """
            {
              "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
              "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
              "source_asset": "USD",
              "source_network_id": "fiat",
              "destination_asset": "USDC",
              "destination_network_id": "ethereum-mainnet",
              "amount": {
                "value": "999999.00",
                "asset": "USD"
              }
            }
            """;
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.platform.sandbox.dakota.xyz/transactions/one-off"))
            .header("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
            .header("X-Idempotency-Key", UUID.randomUUID().toString())
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();
            
        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
    }
}

Simulated Processing Times

In sandbox, transactions complete faster:
  • Onramp: 30 seconds to 2 minutes
  • Offramp: 1-3 minutes
  • Swap: 10-30 seconds
  • Failed: Immediate

Webhook Testing

Test Webhook Events

Trigger test webhooks manually:
POST /v1/test/webhooks/trigger
{
  "event": "transaction.status.updated",
  "webhook_target_id": "wh_test_123",
  "data": {
    "transaction_id": "31TgvtxUdXi95dUN4M8X1rhSCNS",
    "status": "completed"
  }
}

Webhook Testing Tools

ngrok for Local Development

# Expose local server
ngrok http 3000

# Use HTTPS URL for webhook registration  
curl -X POST https://api.platform.sandbox.dakota.xyz/webhooks/targets \
  -H "X-API-Key: your-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 webhook.site
  2. Copy the unique URL
  3. Register it as your webhook endpoint
  4. View real-time webhook deliveries

Integration Testing Checklist

Authentication

  • API keys work in sandbox
  • Error handling for invalid keys
  • Rate limiting behavior
  • Proper header format

Customer Management

  • Create customers successfully
  • Handle validation errors
  • KYB flow integration
  • Status polling/webhooks

Transaction Processing

  • Successful transaction creation
  • Transaction estimation accuracy
  • Error handling (insufficient funds, invalid addresses)
  • Status tracking and webhooks
  • Idempotency key usage

Webhook Handling

  • Webhook signature verification
  • Event processing logic
  • Duplicate event handling
  • Retry mechanisms
  • Error logging

Error Simulation

Force Specific Errors

Use special values to trigger errors in sandbox:

Transaction Errors

// Force insufficient funds error
{ source_amount: { value: "999999.00", currency: "USD" }}

// Force invalid address error  
{ destination: { address: "invalid_address" }}

// Force rate limit error
// Make 150+ requests in 1 minute with same key

KYB Errors

// Force KYB rejection
{ email: "rejected@test.com" }

// Force timeout
{ email: "timeout@test.com" }

Load Testing

Rate Limit Testing

Test your rate limit handling:
cURL
# Simple rate limit test - run this script
for i in {1..150}; do
  curl -X GET https://api.platform.sandbox.dakota.xyz/customers \
    -H "X-API-Key: AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=" &
done
wait
JavaScript
async function testRateLimit() {
  const promises = [];
  
  // Make 150 concurrent requests
  for (let i = 0; i < 150; i++) {
    promises.push(
      fetch('https://api.platform.sandbox.dakota.xyz/customers', {
        headers: {
          'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc='
        }
      }).catch(err => err)
    );
  }
  
  const results = await Promise.all(promises);
  const rateLimited = results.filter(r => r.status === 429);
  
  console.log(`${rateLimited.length} requests were rate limited`);
}
Python
import asyncio
import aiohttp

async def test_rate_limit():
    async with aiohttp.ClientSession() as session:
        tasks = []
        
        # Make 150 concurrent requests
        for i in range(150):
            task = session.get(
                'https://api.platform.sandbox.dakota.xyz/customers',
                headers={'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc='}
            )
            tasks.append(task)
        
        responses = await asyncio.gather(*tasks, return_exceptions=True)
        rate_limited = [r for r in responses if hasattr(r, 'status') and r.status == 429]
        
        print(f'{len(rate_limited)} requests were rate limited')

# Run the test
asyncio.run(test_rate_limit())

Transaction Volume Testing

cURL
# Transaction volume test - run multiple transactions
for i in {1..10}; do
  curl -X POST https://api.platform.sandbox.dakota.xyz/transactions/one-off \
    -H "X-API-Key: AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=" \
    -H "X-Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
      "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
      "source_asset": "USD",
      "source_network_id": "fiat",
      "destination_asset": "USDC",
      "destination_network_id": "ethereum-mainnet",
      "amount": {
        "value": "10.00",
        "asset": "USD"
      }
    }' &
done
wait
JavaScript
async function testTransactionVolume() {
  const transactions = [];
  
  for (let i = 0; i < 10; i++) {
    transactions.push(
      fetch('https://api.platform.sandbox.dakota.xyz/transactions/one-off', {
        method: 'POST',
        headers: {
          'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
          'X-Idempotency-Key': `test_${i}_${Date.now()}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          customer_id: '31TgvwFzi3rstV0DEDzQtuBfwFR',
          destination_id: '31TgvySz1ARnqMZUdbuxykqqxGV',
          source_asset: 'USD',
          source_network_id: 'fiat',
          destination_asset: 'USDC',
          destination_network_id: 'ethereum-mainnet',
          amount: {
            value: '10.00',
            asset: 'USD'
          }
        })
      })
    );
  }
  
  const results = await Promise.allSettled(transactions);
  console.log(`${results.filter(r => r.status === 'fulfilled').length} succeeded`);
}
Python
import asyncio
import aiohttp
import uuid

async def test_transaction_volume():
    async with aiohttp.ClientSession() as session:
        tasks = []
        
        for i in range(10):
            task = session.post(
                'https://api.platform.sandbox.dakota.xyz/transactions/one-off',
                headers={
                    'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=',
                    'X-Idempotency-Key': str(uuid.uuid4()),
                    'Content-Type': 'application/json'
                },
                json={
                    'customer_id': '31TgvwFzi3rstV0DEDzQtuBfwFR',
                    'destination_id': '31TgvySz1ARnqMZUdbuxykqqxGV',
                    'source_asset': 'USD',
                    'source_network_id': 'fiat',
                    'destination_asset': 'USDC',
                    'destination_network_id': 'ethereum-mainnet',
                    'amount': {
                        'value': '10.00',
                        'asset': 'USD'
                    }
                }
            )
            tasks.append(task)
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        succeeded = [r for r in results if not isinstance(r, Exception)]
        
        print(f'{len(succeeded)} transactions succeeded')

# Run the test
asyncio.run(test_transaction_volume())
Go
package main

import (
    "bytes"
    "fmt"
    "net/http"
    "sync"
    "github.com/google/uuid"
)

func main() {
    client := &http.Client{}
    var wg sync.WaitGroup
    successCount := 0
    mu := sync.Mutex{}
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(idx int) {
            defer wg.Done()
            
            body := bytes.NewBufferString(`{
                "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
                "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
                "source_asset": "USD",
                "source_network_id": "fiat",
                "destination_asset": "USDC",
                "destination_network_id": "ethereum-mainnet",
                "amount": {
                    "value": "10.00",
                    "asset": "USD"
                }
            }`)
            
            req, _ := http.NewRequest("POST", "https://api.platform.sandbox.dakota.xyz/transactions/one-off", body)
            req.Header.Add("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
            req.Header.Add("X-Idempotency-Key", uuid.New().String())
            req.Header.Add("Content-Type", "application/json")
            
            resp, err := client.Do(req)
            if err == nil && resp.StatusCode < 400 {
                mu.Lock()
                successCount++
                mu.Unlock()
            }
            if resp != nil {
                resp.Body.Close()
            }
        }(i)
    }
    
    wg.Wait()
    fmt.Printf("%d transactions succeeded\n", successCount)
}
Rust
use reqwest::header::{HeaderMap, HeaderValue};
use uuid::Uuid;
use serde_json::json;
use tokio::task::JoinSet;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let mut tasks = JoinSet::new();
    
    for i in 0..10 {
        let client_clone = client.clone();
        tasks.spawn(async move {
            let mut headers = HeaderMap::new();
            headers.insert("X-API-Key", HeaderValue::from_static("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc="));
            headers.insert("X-Idempotency-Key", HeaderValue::from_str(&Uuid::new_v4().to_string()).unwrap());
            headers.insert("Content-Type", HeaderValue::from_static("application/json"));
            
            let body = json!({
                "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
                "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
                "source_asset": "USD",
                "source_network_id": "fiat",
                "destination_asset": "USDC",
                "destination_network_id": "ethereum-mainnet",
                "amount": {
                    "value": "10.00",
                    "asset": "USD"
                }
            });
            
            client_clone
                .post("https://api.platform.sandbox.dakota.xyz/transactions/one-off")
                .headers(headers)
                .json(&body)
                .send()
                .await
                .is_ok()
        });
    }
    
    let mut success_count = 0;
    while let Some(result) = tasks.join_next().await {
        if result.unwrap_or(false) {
            success_count += 1;
        }
    }
    
    println!("{} transactions succeeded", success_count);
    Ok(())
}
Java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
import java.time.Duration;

public class DakotaTransactionVolumeTest {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(30))
            .build();
        
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        CompletableFuture<Integer>[] futures = IntStream.range(0, 10)
            .mapToObj(i -> CompletableFuture.supplyAsync(() -> {
                try {
                    String body = """
                        {
                          "customer_id": "31TgvwFzi3rstV0DEDzQtuBfwFR",
                          "destination_id": "31TgvySz1ARnqMZUdbuxykqqxGV",
                          "source_asset": "USD",
                          "source_network_id": "fiat",
                          "destination_asset": "USDC",
                          "destination_network_id": "ethereum-mainnet",
                          "amount": {
                            "value": "10.00",
                            "asset": "USD"
                          }
                        }
                        """;
                    
                    HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create("https://api.platform.sandbox.dakota.xyz/transactions/one-off"))
                        .header("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
                        .header("X-Idempotency-Key", UUID.randomUUID().toString())
                        .header("Content-Type", "application/json")
                        .POST(HttpRequest.BodyPublishers.ofString(body))
                        .build();
                        
                    HttpResponse<String> response = client.send(request, 
                        HttpResponse.BodyHandlers.ofString());
                        
                    return response.statusCode() < 400 ? 1 : 0;
                } catch (Exception e) {
                    return 0;
                }
            }, executor))
            .toArray(CompletableFuture[]::new);
        
        int successCount = CompletableFuture.allOf(futures)
            .thenApply(v -> IntStream.range(0, futures.length)
                .map(i -> futures[i].join())
                .sum())
            .get();
        
        System.out.println(successCount + " transactions succeeded");
        executor.shutdown();
    }
}

Production Readiness

Pre-Launch Checklist

Before switching to production:
  • All sandbox tests passing
  • Error handling comprehensive
  • Webhook endpoints secured and tested
  • Rate limiting handled gracefully
  • Logging and monitoring in place
  • Security review completed
  • Backup/recovery procedures tested

Migration to Production

  1. Update Configuration
    • Change base URL to https://api.platform.dakota.xyz
    • Replace development API keys with production keys
    • Update webhook URLs if needed
  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. Gradual Rollout
    • Start with small transaction amounts
    • Monitor error rates closely
    • Have rollback plan ready

Monitoring and Debugging

Logging Best Practices

// Log all API calls
logger.info('Dakota Platform API call', {
  method: 'POST',
  endpoint: '/accounts',
  account_type: 'onramp',
  destination_asset: 'USDC'
});

// Log webhook events
logger.info('Webhook received', {
  event: event.event,
  webhook_id: event.id,
  data: event.data
});

Debug Mode

Enable detailed request/response logging:
const dakota = new DakotaSDK({
  baseURL: 'https://api.platform.sandbox.dakota.xyz',
  apiKey: 'your-api-key',
  debug: true // Logs all HTTP requests/responses
});

Required Headers

X-Idempotency-Key is Required for ALL POST EndpointsPer the OpenAPI specification, the X-Idempotency-Key header is required for all POST endpoints to ensure request idempotency. This includes every create operation in the API.Requests without this header will fail.
# Always include X-Idempotency-Key on ALL POST requests
curl -X POST https://api.platform.sandbox.dakota.xyz/customers \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{...}'
The idempotency key should be a unique UUID. If the same key is used within a certain time window, the original response will be returned instead of executing the request again.

Common Issues and Solutions

Issue: HTTP 400 on POST Requests

Cause: The X-Idempotency-Key header is required for ALL POST endpoints but is missing. Solution: Add X-Idempotency-Key: <unique-uuid> header to every POST request. Generate a new UUID for each unique request.

Issue: Webhooks Not Received

Solution: Check webhook endpoint accessibility and response codes. Use ngrok or webhook.site for local testing.

Issue: Transactions Stuck in Processing

Solution: Use the simulation endpoints to progress transaction state, or add X-Sandbox-Instant-Completion: true header when creating transactions.

Issue: KYB Never Completes

Solution: KYB auto-approves after 5 seconds in sandbox. If using X-Sandbox-Skip-Auto-Approval: true, manually approve via POST /sandbox/customers/{id}/kyb/simulate.

Issue: Rate Limits Hit Quickly

Solution: Implement exponential backoff and request queuing.

Issue: Currency Mismatch Error on Account Simulation

Cause: The currency field in simulate request doesn’t match the account’s input_asset. Solution: For onramp accounts (USD→USDC), use currency: "USD". For offramp accounts (USDC→USD), use currency: "USDC".

Next Steps

After thorough testing:
  1. Authentication - Review production security practices
  2. Customer Onboarding - Set up production KYB flows
  3. Transactions - Process real transactions
  4. Webhooks - Configure production webhook endpoints

API Reference

For testing-specific documentation, see: