Errors

Chain uses standard HTTP status codes and returns structured error responses. This guide covers error formats, common codes, and best practices for handling them.

Error Response Format

All error responses return a consistent JSON object with an error key.

Error response
{
  "error": {
    "code": "insufficient_funds",
    "message": "Wallet wal_xyz789 has insufficient funds for this transaction.",
    "status": 422,
    "param": "amount",
    "request_id": "req_abc123"
  }
}

HTTP Status Codes

CodeMeaningAction
200OKRequest succeeded.
201CreatedResource created successfully.
400Bad RequestCheck request body for validation errors.
401UnauthorizedVerify your API key and Authorization header.
403ForbiddenYour API key does not have access to this resource.
404Not FoundThe requested resource does not exist.
409ConflictRequest conflicts with current state (e.g. duplicate idempotency key).
422UnprocessableRequest is valid but cannot be processed (e.g. insufficient funds).
429Rate LimitedToo many requests. Back off and retry with exponential delay.
500Server ErrorSomething went wrong on our end. Retry or contact support.

Error Codes

Authentication

CodeDescription
invalid_api_keyThe API key provided is invalid or revoked.
expired_api_keyThe API key has expired. Generate a new one.
missing_authorizationNo Authorization header provided.
insufficient_permissionsYour key does not have the required permissions.

Validation

CodeDescription
invalid_parameterA request parameter is invalid. Check the param field.
missing_parameterA required parameter is missing.
invalid_currencyThe specified currency is not supported.
amount_too_smallAmount is below the minimum transaction size.
amount_too_largeAmount exceeds the maximum transaction size.

Payment

CodeDescription
insufficient_fundsWallet does not have enough funds for this transaction.
bank_account_not_linkedThe specified bank account has not been linked.
payee_not_verifiedPayee has not completed verification.
payment_limit_exceededTransaction exceeds your account or card limits.
duplicate_transactionA transaction with this idempotency key already exists.

Card

CodeDescription
card_frozenThe card is frozen and cannot process transactions.
card_cancelledThe card has been cancelled.
card_limit_exceededTransaction exceeds card spending limits.
mcc_not_allowedMerchant category is not allowed for this card.

Business & Compliance

CodeDescription
business_not_verifiedBusiness has not completed KYB verification.
business_suspendedBusiness account has been suspended.
compliance_holdTransaction held for compliance review.

Rate Limiting

CodeDescription
rate_limit_exceededToo many requests. Retry after the delay in the Retry-After header.
concurrent_request_limitToo many concurrent requests. Reduce parallel calls.

Best Practices

Use idempotency keys

Include X-Idempotency-Key headers on mutating requests to safely retry without creating duplicate resources.

Implement exponential backoff

When you receive a 429 or 5xx error, wait with exponential backoff before retrying. Our SDKs handle this automatically.

Check the request_id

Every error response includes a request_id. Include this when contacting support for faster resolution.

Handle specific error codes

Don't just check HTTP status codes. Use the code field to handle specific error scenarios and provide better error messages to your users.

Example: Handling Errors

Node.js error handling
import Chain, { ChainError } from '@chain/node';

const chain = new Chain('sk_live_...');

try {
  const payout = await chain.payouts.create({
    amount: 50000,
    currency: 'USDC',
    payee_id: 'payee_abc',
    wallet_id: 'wal_xyz789',
  });
} catch (err) {
  if (err instanceof ChainError) {
    switch (err.code) {
      case 'insufficient_funds':
        // Show user a friendly message
        showError('Not enough funds. Please add more to your wallet.');
        break;
      case 'payee_not_verified':
        // Redirect to verification
        redirect('/payees/payee_abc/verify');
        break;
      case 'rate_limit_exceeded':
        // Retry after delay
        await sleep(err.retryAfter * 1000);
        break;
      default:
        // Log and report
        logger.error(`Chain API error: ${err.code}`, {
          requestId: err.requestId,
          status: err.status,
        });
    }
  }
}
globe
icon

Pax Dollar

USDP

icon

Ripple USD

RLUSD

icon

Dollar

USD

icon

Tether

USDT

icon

Pounds

GBP

icon

Euro

Eur