ERROR CODES

Stable, machine-readable error codes for every WDK + USDT0 failure mode. Each code tells you exactly what happened and what to do about it.

Error Payload Format

All error responses use this JSON structure. The code field is machine-readable and stable across API versions. The message is human-readable and may change.

HTTP/1.1 410 Gone
Content-Type: application/json

{
  "error": {
    "code": "P402_QUOTE_EXPIRED",
    "message": "The quote q_123 expired at 2026-04-16T12:01:00.000Z.",
    "details": {
      "quoteId": "q_123",
      "expiredAt": "2026-04-16T12:01:00.000Z"
    }
  }
}

HTTP Status Mapping

HTTP Status Meaning Retry?
400 Bad Request Malformed payload. Your code is wrong. No — fix the request first.
401 Unauthorized Signature or authentication failure. No — re-sign with correct parameters.
402 Payment Required Insufficient balance. No — top up the wallet first.
409 Conflict Replay detected — nonce already used. No — generate a new nonce.
410 Gone Quote expired. Yes — re-quote and resubmit.
422 Unprocessable Policy denied the route. Yes — with a different route.
503 Service Unavailable No route available or receipt delayed. Yes — with backoff.
504 Gateway Timeout Settlement not finalized in SLA. Poll receipt endpoint; do not retry settle.

Full Error Reference

P402_QUOTE_EXPIRED HTTP 410

Trigger
The quote TTL (60 seconds) elapsed before the settlement was submitted.

Recovery
Re-request the quote (/api/v1/liquidity/quote) and prompt the user to sign within the TTL. Do not retry the settle call with the expired quoteId.

P402_ROUTE_UNAVAILABLE HTTP 503

Trigger
No viable route exists for the requested asset and constraints at this time.

Recovery
Relax constraints (increase maxFeeBps or maxLatencyMs), or change the sourceAssets order. Retry with exponential backoff. If persistent, fall back to USDC.

P402_POLICY_BLOCKED_ROUTE HTTP 422

Trigger
The policy engine denied the selected route (e.g. route is on a blocked jurisdiction).

Recovery
Select a different route from the quote options that is compliant with your account policy. Do not retry the same routeId.

P402_SIGNATURE_REJECTED HTTP 401

Trigger
The EIP-712 signature failed verification. Common causes: wrong chainId, wrong contract address, wrong nonce, or malformed signature bytes.

Recovery
Rebuild the authorization object from scratch. Verify chainId matches the token's deployment chain. Re-sign with the WDK adapter and resubmit.

P402_AUTH_INVALID HTTP 400

Trigger
The authorization payload is structurally malformed (missing fields, wrong types, invalid address format).

Recovery
Check all required fields: from, to, value, validAfter, validBefore, nonce. Ensure nonce is a 0x-prefixed 32-byte hex string.

P402_INSUFFICIENT_BALANCE HTTP 402

Trigger
The wallet does not hold enough of the source asset to cover amount + fees.

Recovery
Check the wallet balance before submitting. Offer a fallback route with a cheaper asset (e.g. try USDC if USDT0 balance is insufficient).

P402_SETTLEMENT_TIMEOUT HTTP 504

Trigger
The on-chain transaction was submitted but not finalized within the SLA window (30 seconds on Base).

Recovery
Poll GET /api/v1/receipts/{receipt_id} with exponential backoff. The transaction may still finalize. If receipt shows settled: true, the payment succeeded.

P402_RECEIPT_UNAVAILABLE HTTP 503

Trigger
Receipt generation is delayed — the transaction settled on-chain but the receipt record is not yet written.

Recovery
Retry GET /api/v1/receipts/{receipt_id} with 2-second intervals for up to 60 seconds. Keep the txHash visible in your UI so users can verify on a block explorer.

P402_REPLAY_DETECTED HTTP 409

Trigger
The nonce in the authorization has already been used in a prior settlement for this wallet.

Recovery
Generate a new nonce (32 random bytes) and re-sign. Never reuse nonces. Use the receipt scheme instead if you need idempotent retries.

Recommended Retry Strategy

const RETRYABLE_CODES = new Set([
  'P402_QUOTE_EXPIRED',
  'P402_ROUTE_UNAVAILABLE',
  'P402_POLICY_BLOCKED_ROUTE',
  'P402_SETTLEMENT_TIMEOUT',
  'P402_RECEIPT_UNAVAILABLE',
]);

async function settleWithRetry(payload: SettlePayload, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch('/api/v1/router/settle', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });

if (response.ok) return response.json();

const error = await response.json();
    const code = error.error?.code;

if (!RETRYABLE_CODES.has(code) || attempt === maxAttempts) {
      throw new Error(`Settlement failed: ${code}`);
    }

// For expired quotes: re-quote before retrying
    if (code === 'P402_QUOTE_EXPIRED') {
      const newQuote = await requestQuote(payload);
      payload = { ...payload, quoteId: newQuote.quoteId, routeId: newQuote.routes[0].routeId };
    }

// Exponential backoff: 1s, 2s, 4s
    await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
  }
}