# 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

```json
{
  "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

```javascript
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
