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 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
- Ask Dakota Support for a Sandbox account login
- Go to the Dashboard:
https://platform.sandbox.dakota.xyz
- Generate test API keys
- 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
| Feature | Production | Sandbox |
|---|
| External API calls | Real third-party providers | Mocked responses |
| Money movement | Real funds | Simulated |
| KYB verification | Real verification | Auto-approved after 5 seconds |
| Compliance checks | Real TRM screening | Configurable mock responses |
| Webhooks | From external providers | Simulated via /sandbox/* endpoints |
| Bank accounts | Real routing/account numbers | Mock data (routing: 123456789) |
| Crypto addresses | Real wallet addresses | Random Ethereum-style addresses |
| Processing times | Minutes to hours | Seconds (configurable) |
Sandbox Behavior Control Headers
The sandbox provides HTTP headers to control behavior, inject errors, and simulate specific scenarios.
| Header | Type | Description |
|---|
X-Sandbox-Instant-Completion | boolean | Complete async operations immediately instead of requiring manual simulation |
X-Sandbox-Skip-Auto-Approval | boolean | Prevent automatic KYB approval (default: KYB auto-approves after 5 seconds) |
X-Sandbox-Scenario | string | Use a predefined test scenario |
X-Sandbox-Error-Status | integer | Inject a specific HTTP error status code (100-599) |
X-Sandbox-Error-Message | string | Custom error message (max 500 characters) |
X-Sandbox-Error-Step | string | Specify 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 -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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:
| Scenario | Description | Effect |
|---|
compliance_block | Transaction blocked by compliance | Returns 403 error at compliance_check step |
insufficient_funds | Source account has insufficient funds | Returns 400 error at transaction_processing step |
slow_processing | Delayed processing | Simulates slower processing times |
document_required | Additional documentation needed | Triggers document requirement flow |
# Example: Test compliance block
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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 Case | Headers to Use |
|---|
| KYB rejection | X-Sandbox-Error-Status: 403 + X-Sandbox-Error-Step: kyb_submission |
| Provider maintenance | X-Sandbox-Error-Status: 503 + X-Sandbox-Error-Step: provider_call |
| Network timeout | X-Sandbox-Error-Status: 504 + X-Sandbox-Error-Step: network_call |
| Rate limiting | X-Sandbox-Error-Status: 429 |
| Invalid account | X-Sandbox-Error-Status: 400 + X-Sandbox-Error-Step: account_validation |
Valid Error Steps
| Step | Description |
|---|
account_validation | During account validation |
compliance_check | During compliance screening |
kyb_submission | During KYB submission |
transaction_processing | During transaction processing |
provider_call | When calling external providers |
network_call | For network operations |
# Example: Inject custom error at specific step
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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
| Field | Type | Required | Description |
|---|
amount | string | Yes | Amount to simulate (e.g., "100.00") |
currency | string | Yes | Currency code - must match the account’s input asset |
reference | string | No | Optional reference for the transaction |
target_state | string | No | Target 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 State | Transaction Status | Description |
|---|
funds_received | pending | Funds received, awaiting processing |
payment_submitted | processing | Payment submitted to provider |
payment_processed | completed | Payment fully processed |
For Offramp (Crypto → Fiat):
| Target State | Transaction Status | Description |
|---|
created | pending | Drain initiated |
pending | processing | Drain in progress |
completed | completed | Drain completed |
# 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"
}'
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
| Field | Type | Required | Description |
|---|
status | string | Yes | New status: approved, rejected, in_review, or expired |
reason | string | No | Optional reason for the status change |
# 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
| Field | Type | Required | Description |
|---|
status | string | Yes | New status: pending, processing, completed, or failed |
error_message | string | Conditional | Error message (required if status is failed) |
# 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 -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 Address | Risk Level | Risk Score |
|---|
Contains BAD | High | 75 |
Contains SCAM | High | 75 |
Contains hack | High | 75 |
Contains review | Medium (Manual Review) | 50 |
Contains manual | Medium (Manual Review) | 50 |
| Normal addresses | Low | 10 |
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:
| Entity | Mock Values |
|---|
| Routing Number | 123456789 (constant) |
| Account Number | Random 10-digit number |
| Bank Name | Dakota Ridge Bank |
| IBAN | Random German IBAN (DE format) |
| IBAN Bank | Dakota Europa Bank |
| Crypto Address | Random Ethereum-style (0x + 40 hex chars) |
| TX Hash | Random (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 Pattern | KYB Result |
|---|
approved@test.com | Auto-approved |
rejected@test.com | Auto-rejected |
pending@test.com | Stays pending |
review@test.com | Requires manual review |
Testing Transactions
Transaction Test Cases
Successful Onramp
# Successful onramp transaction
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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"
}
}'
// Successful onramp transaction
const transaction = await fetch('https://api.platform.sandbox.dakota.xyz/transactions', {
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'
}
})
});
# Successful onramp transaction
transaction_response = requests.post(
'https://api.platform.sandbox.dakota.xyz/transactions',
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)
# Transaction that will fail due to large amount
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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"
}
}'
// Transaction that will fail due to large amount
const transaction = await fetch('https://api.platform.sandbox.dakota.xyz/transactions', {
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'
}
})
});
# Transaction that will fail due to large amount
transaction_response = requests.post(
'https://api.platform.sandbox.dakota.xyz/transactions',
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'
}
}
)
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", 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()
}
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")
.headers(headers)
.json(&body)
.send()
.await?;
Ok(())
}
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"))
.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.auto.updated",
"webhook_target_id": "wh_test_123",
"data": {
"transaction_id": "31TgvtxUdXi95dUN4M8X1rhSCNS",
"status": "completed"
}
}
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
- Visit webhook.site
- Copy the unique URL
- Register it as your webhook endpoint
- View real-time webhook deliveries
Integration Testing Checklist
Authentication
Customer Management
Transaction Processing
Webhook Handling
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:
# 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
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`);
}
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
# Transaction volume test - run multiple transactions
for i in {1..10}; do
curl -X POST https://api.platform.sandbox.dakota.xyz/transactions \
-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
async function testTransactionVolume() {
const transactions = [];
for (let i = 0; i < 10; i++) {
transactions.push(
fetch('https://api.platform.sandbox.dakota.xyz/transactions', {
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`);
}
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',
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())
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", 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)
}
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")
.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(())
}
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"))
.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:
Migration to Production
-
Update Configuration
- Change base URL to
https://api.platform.dakota.xyz
- Replace development API keys with production keys
- Update webhook URLs if needed
-
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
-
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
});
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:
- Authentication - Review production security practices
- Customer Onboarding - Set up production KYB flows
- Transactions - Process real transactions
- Webhooks - Configure production webhook endpoints
API Reference
For testing-specific documentation, see: