Error Handling

Last updated: 2026-10-22

DCE API errors use standard HTTP status codes and JSON bodies. This guide explains what to expect and how to handle failures safely.

Response patterns

Success responses are route-specific JSON objects.

Error responses commonly include:

{
  "error": "Message describing failure"
}

Some routes add a human-readable message alongside error, and validation routes may include structured fields such as details.

Status code guide

CodeMeaningTypical action
200SuccessProcess response
400Validation or business-rule failureFix request payload/parameters
401Missing/invalid authCheck API key and header
403Permission denied or merchant account not activeVerify key scope/role and account status
404Resource not foundValidate IDs/paths
409Conflict (e.g., duplicate referenceId)Do not retry with the same reference unless you intend a new action
410Gone (e.g., consumed/expired deposit token)Regenerate token/session
413Payload too largeReduce payload size
429Rate limit reachedRetry with backoff
500Internal server errorRetry later and contact support if persistent
503Service temporarily unavailable (e.g., withdrawals paused)Retry shortly or contact support

Common error cases

409 — Duplicate referenceId (withdrawals)

Your referenceId is unique per merchant account and a second payout is never created for it. Retrying the same request (same referenceId, amount, currency, network, destination) returns 200 with the original withdrawal and "replayed": true — safe after a timeout. Reusing the referenceId for a different request returns 409:

{
  "error": "Duplicate referenceId",
  "message": "A withdrawal with this referenceId already exists for your merchant account with different parameters. Use a new referenceId to submit a new withdrawal.",
  "withdrawalId": "cmdl8u2xq0001abcd1234efgh"
}

Treat this as "reference already taken": use a new referenceId for a new payout. If a withdrawal fails, its referenceId is automatically released so the same reference can be retried.

403 — Merchant account not active

Suspended or inactive merchant accounts cannot initiate withdrawals or create deposit URLs. Withdrawal attempts return:

{
  "error": "Merchant account is not active"
}

This is not retryable — contact support (or your reseller) to reactivate the account.

400 — Disabled or unknown (currency, network) pair

Balances and payments are per-(currency, network) pair, and each pair must be enabled for the requested direction. Currently only USDT on TRX (and its Nile testnet, TRX_NILE) is enabled; other pairs are coming soon / on request. Requests referencing a disabled pair are rejected:

{
  "error": "Withdrawals of USDT on ETH are not supported"
}
{
  "error": "Deposits of USDC on SOL are not supported"
}

400 — Insufficient per-chain balance

Withdrawals draw only from the balance on the requested network — funds on other networks cannot be used. The rejection includes the per-chain context and the fee quote:

{
  "error": "Insufficient balance",
  "message": "Insufficient USDT balance on TRX. Deposits and withdrawals are per-chain: funds on other networks cannot be used.",
  "available": "50.25",
  "requested": "100.50",
  "currency": "USDT",
  "network": "TRX",
  "feeInfo": {
    "grossAmount": "100.5",
    "netAmount": "98.5",
    "totalFees": "2",
    "feeBreakdown": {
      "baseFee": "1",
      "markupRate": "0",
      "markupAmount": "1",
      "networkFee": "1",
      "totalFee": "2"
    }
  }
}

Remember that fees are charged on top: the total debit is amount + commission + networkFee.

503 — Withdrawals temporarily unavailable

Withdrawals may be temporarily paused platform-side. Nothing is created and your referenceId is not consumed, so the same request can be retried later:

{
  "error": "Withdrawal temporarily unavailable",
  "message": "Withdrawals are temporarily unavailable. Please try again shortly or contact support."
}

Payout failed after acceptance (funds returned)

POST /api/withdrawals answers once the withdrawal is recorded; the on-chain payout is sent right after. If it cannot be sent, you receive a withdrawal.failed webhook (and the withdrawal shows FAILED), the reserved amount including fees is returned to your balance, and the referenceId is released for retry. (Before v1.4 this case was a synchronous 500 Payout initiation failed.)

400 — Validation errors

Malformed payloads return field-level details:

{
  "error": "Invalid request data",
  "details": [
    {
      "field": "amount",
      "message": "Amount must be a numeric string with up to 18 decimal places"
    }
  ]
}

Retry strategy

Retry only transient failures:

  • 429, 500, 503, network timeout/connection issues

Do not blindly retry:

  • 400, 401, 403, 409, most 404 (unless eventual consistency is expected)

For 409 Duplicate referenceId specifically: the original request already exists — query it instead of resubmitting.

Recommended backoff:

  • Exponential backoff + jitter (for example 1s, 2s, 4s, 8s with randomness)
  • Max attempts: 3-5 depending on operation criticality

Safe integration patterns

  • Treat all amount fields as decimal strings (send them as strings too, e.g. "100.50").
  • Use your own idempotency reference (referenceId) to prevent duplicate business actions — reuse of a referenceId never creates a second payout.
  • Webhook delivery is at-least-once: deduplicate on the payload's eventId and make handlers idempotent.
  • Log request context (route, status, payload hash, timestamps) without logging secrets.

Example error handling (Node.js)

const response = await fetch(url, options);
const body = await response.json().catch(() => ({}));

if (!response.ok) {
  if ([429, 500, 503].includes(response.status)) {
    // retry with backoff
  } else if (response.status === 409) {
    // duplicate referenceId — the withdrawal already exists; look it up
  } else {
    // handle as non-retryable
    throw new Error(body.message || body.error || `HTTP ${response.status}`);
  }
}

When to contact support

Reach out with:

  • environment (staging or production)
  • endpoint + approximate timestamp (UTC)
  • status code + response body snippet
  • the X-Request-Id response header (your own X-Correlation-Id if you sent a plain token — letters, digits, ., _, -, up to 128 characters)

Contact: [email protected]


Did this page help you?