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

Code Meaning Action
200 OK Request succeeded.
201 Created Resource created successfully.
400 Bad Request Check request body for validation errors.
401 Unauthorized Verify your API key and Authorization header.
403 Forbidden Your API key does not have access to this resource.
404 Not Found The requested resource does not exist.
409 Conflict Request conflicts with current state (e.g. duplicate idempotency key).
422 Unprocessable Request is valid but cannot be processed (e.g. insufficient funds).
429 Rate Limited Too many requests. Back off and retry with exponential delay.
500 Server Error Something went wrong on our end. Retry or contact support.

Error Codes

Authentication

Code Description
invalid_api_key The API key provided is invalid or revoked.
expired_api_key The API key has expired. Generate a new one.
missing_authorization No Authorization header provided.
insufficient_permissions Your key does not have the required permissions.

Validation

Code Description
invalid_parameter A request parameter is invalid. Check the param field.
missing_parameter A required parameter is missing.
invalid_currency The specified currency is not supported.
amount_too_small Amount is below the minimum transaction size.
amount_too_large Amount exceeds the maximum transaction size.

Payment

Code Description
insufficient_funds Wallet does not have enough funds for this transaction.
bank_account_not_linked The specified bank account has not been linked.
payee_not_verified Payee has not completed verification.
payment_limit_exceeded Transaction exceeds your account or card limits.
duplicate_transaction A transaction with this idempotency key already exists.

Card

Code Description
card_frozen The card is frozen and cannot process transactions.
card_cancelled The card has been cancelled.
card_limit_exceeded Transaction exceeds card spending limits.
mcc_not_allowed Merchant category is not allowed for this card.

Business & Compliance

Code Description
business_not_verified Business has not completed KYB verification.
business_suspended Business account has been suspended.
compliance_hold Transaction held for compliance review.

Rate Limiting

Code Description
rate_limit_exceeded Too many requests. Retry after the delay in the Retry-After header.
concurrent_request_limit Too 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,
        });
    }
  }
}

Pax Dollar

USDP

Ripple USD

RLUSD

Dollar

USD

Tether

USDT

Pounds

GBP

Euro

Eur