> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monei.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Comprehensive guide to handling errors in Monei infrastructure

## Overview

Proper error handling is essential for building reliable applications on Monei infrastructure. This guide covers all error types, response formats, recovery strategies, and best practices.

**What you'll learn:**

* Error response structure
* Common error types and codes
* Error handling patterns
* Retry strategies
* Debugging techniques

***

## Error Response Structure

All Monei API errors follow a consistent format:

```json theme={null}
{
  "statusCode": 400,
  "message": "Insufficient balance for transaction",
  "error": {
    "details": {
        "required": 150.50,
        "available": 100.00,
        "currency": "USDT"
    }
  }
}
```

### Response Fields

| Field        | Type   | Description                                     |
| ------------ | ------ | ----------------------------------------------- |
| `statusCode` | number | HTTP status code (400, 401, 403, 404, 429, 500) |
| `message`    | string | Human-readable error description                |
| `error`      | string | Error category                                  |
| `details`    | object | Additional error context (optional)             |

***

## HTTP Status Codes

<Tabs>
  <Tab title="Client Errors (4xx)">
    **400 Bad Request**

    * Invalid request parameters
    * Malformed JSON
    * Missing required fields

    **401 Unauthorized**

    * Invalid API key
    * Expired token
    * Missing authentication

    **403 Forbidden**

    * Insufficient permissions
    * KYC tier limit exceeded
    * Operation not allowed

    **404 Not Found**

    * Resource doesn't exist
    * Invalid endpoint
    * Transaction not found

    **429 Too Many Requests**

    * Rate limit exceeded
    * Too many requests per minute
    * Retry after specified time
  </Tab>

  <Tab title="Server Errors (5xx)">
    **500 Internal Server Error**

    * Unexpected server error
    * Database connection failure
    * Processing error

    **502 Bad Gateway**

    * Upstream service error
    * Blockchain RPC failure
    * Third-party API down

    **503 Service Unavailable**

    * Maintenance mode
    * Temporary outage
    * System overload

    **504 Gateway Timeout**

    * Request timeout
    * Slow blockchain confirmation
    * External service timeout
  </Tab>
</Tabs>

***

## Common Error Types

### Authentication Errors

<CodeGroup>
  ```javascript Node.js theme={null}
  import MoneiSDK from 'monei-sdk';

  try {
    const monei = new MoneiSDK({
      apiKey: process.env.MONEI_API_KEY,
    });
    
    const account = await monei.user.me();
  } catch (error) {
    if (error.statusCode === 401) {
      console.error('Authentication failed');
      console.error('Reason:', error.message);
      
      // Common causes:
      // - Invalid API key
      // - Expired session
      // - Missing credentials
      
      // Solution: Verify API key and regenerate if needed
    }
  }
  ```

  ```python Python theme={null}
  from monei import MoneiClient
  from monei.exceptions import UnauthorizedError

  try:
      monei = MoneiClient(
          api_key=os.getenv('MONEI_API_KEY')
      )
      
      account = monei.user.me()
  except UnauthorizedError as error:
      print('Authentication failed')
      print(f'Reason: {error.message}')
      
      # Verify API key and regenerate if needed
  ```
</CodeGroup>

**Error Code:** `UNAUTHORIZED`

**Common Messages:**

* "Invalid API key"
* "API key expired"
* "Missing authentication header"

**Solutions:**

* Verify API key is correct
* Check environment variable
* Regenerate API key if compromised
* Ensure proper header format: `x-api-key: YOUR_KEY`

***

### Insufficient Balance

<CodeGroup>
  ```javascript Node.js theme={null}
  try {
    const tx = await monei.evm.sendToken({
      to: '0xRecipientAddress',
      tokenAddress: '0xUSDTAddress',
      amount: '1000',
      chainId: 56
    });
  } catch (error) {
    if (error.code === 'INSUFFICIENT_BALANCE') {
      console.error('Not enough funds');
      console.error('Required:', error.details.required);
      console.error('Available:', error.details.available);
      
      // Check both token balance and gas balance
      const balance = await monei.evm.getBalance({
        tokenAddress: '0xUSDTAddress',
        chainId: 56
      });
      
      const gasBalance = await monei.evm.getBalance({
        tokenAddress: 'native', // BNB for BSC
        chainId: 56
      });
      
      console.log('Token balance:', balance.amount);
      console.log('Gas balance:', gasBalance.amount);
    }
  }
  ```

  ```python Python theme={null}
  from monei.exceptions import InsufficientBalanceError

  try:
      tx = monei.evm.send_token(
          to='0xRecipientAddress',
          token_address='0xUSDTAddress',
          amount='1000',
          chain_id=56
      )
  except InsufficientBalanceError as error:
      print('Not enough funds')
      print(f'Required: {error.details.required}')
      print(f'Available: {error.details.available}')
      
      # Check balances
      balance = monei.evm.get_balance(
          token_address='0xUSDTAddress',
          chain_id=56
      )
      
      gas_balance = monei.evm.get_balance(
          token_address='native',
          chain_id=56
      )
  ```
</CodeGroup>

**Error Code:** `INSUFFICIENT_BALANCE`

**Common Causes:**

* Not enough token balance
* Insufficient native token for gas
* Reserved balance from pending transactions

**Solutions:**

* Check available balance vs. required amount
* Ensure enough native token for gas fees
* Wait for pending transactions to complete
* Fund wallet before retrying

***

### Invalid Recipient

<CodeGroup>
  ```javascript Node.js theme={null}
  try {
    const tx = await monei.evm.sendToken({
      to: 'invalid-address',
      tokenAddress: '0xUSDTAddress',
      amount: '100',
      chainId: 56
    });
  } catch (error) {
    if (error.code === 'INVALID_RECIPIENT') {
      console.error('Invalid recipient address');
      console.error('Reason:', error.details.reason);
      
      // Validate address before sending
      const isValid = await monei.evm.validateAddress({
        address: recipientAddress,
        chainId: 56
      });
      
      if (!isValid.valid) {
        console.error('Address validation failed:', isValid.reason);
        // Reasons: wrong format, wrong network, contract without ABI
      }
    }
  }
  ```

  ```python Python theme={null}
  from monei.exceptions import InvalidRecipientError

  try:
      tx = monei.evm.send_token(
          to='invalid-address',
          token_address='0xUSDTAddress',
          amount='100',
          chain_id=56
      )
  except InvalidRecipientError as error:
      print('Invalid recipient address')
      print(f'Reason: {error.details.reason}')
      
      # Validate address
      is_valid = monei.evm.validate_address(
          address=recipient_address,
          chain_id=56
      )
  ```
</CodeGroup>

**Error Code:** `INVALID_RECIPIENT`

**Common Causes:**

* Malformed address format
* Wrong network for address
* Contract address without ABI
* Checksum mismatch

**Solutions:**

* Validate address format before sending
* Verify network matches recipient
* Use address validation endpoint
* Double-check copy/paste

***

### KYC Limit Exceeded

<CodeGroup>
  ```javascript Node.js theme={null}
  try {
    const swap = await monei.offramp.swap({
      token: 'USDC',
      amount: 500000, // Exceeds tier limit
      network: 'polygon',
      fiatCurrency: 'NGN',
      bankCode: 'GTBINGLA',
      accountNumber: '0123456789'
    });
  } catch (error) {
    if (error.code === 'KYC_LIMIT_EXCEEDED') {
      console.error('Transaction exceeds KYC limits');
      
      // Check current limits
      const limits = await monei.user.getKycLimits();
      
      console.log('Current Tier:', limits.tier);
      console.log('Daily Limit:', limits.dailyLimit);
      console.log('Used Today:', limits.dailyUsed);
      console.log('Remaining:', limits.dailyLimit - limits.dailyUsed);
      
      // Suggest upgrade
      console.log('Upgrade to Tier', limits.nextTier, 'for higher limits');
    }
  }
  ```

  ```python Python theme={null}
  from monei.exceptions import KycLimitExceededError

  try:
      swap = monei.offramp.swap(
          token='USDC',
          amount=500000,
          network='polygon',
          fiat_currency='NGN',
          bank_code='GTBINGLA',
          account_number='0123456789'
      )
  except KycLimitExceededError as error:
      print('Transaction exceeds KYC limits')
      
      # Check limits
      limits = monei.user.get_kyc_limits()
      
      print(f'Current Tier: {limits.tier}')
      print(f'Daily Limit: ₦{limits.daily_limit}')
      print(f'Used Today: ₦{limits.daily_used}')
  ```
</CodeGroup>

**Error Code:** `KYC_LIMIT_EXCEEDED`

**Limits by Tier:**

* Tier 1: ₦200,000/day
* Tier 2: ₦500,000/day
* Tier 3: ₦2,000,000/day

**Solutions:**

* Check remaining daily limit
* Split transaction across multiple days
* Upgrade KYC tier
* Use smaller amounts

***

### Rate Limit Exceeded

<CodeGroup>
  ```javascript Node.js theme={null}
  try {
    const transactions = await monei.transactions.getAll();
  } catch (error) {
    if (error.statusCode === 429) {
      console.error('Rate limit exceeded');
      
      // Get retry information
      const retryAfter = error.headers['retry-after']; // seconds
      const limit = error.headers['x-ratelimit-limit'];
      const remaining = error.headers['x-ratelimit-remaining'];
      const reset = error.headers['x-ratelimit-reset']; // timestamp
      
      console.log(`Retry after ${retryAfter} seconds`);
      console.log(`Rate limit: ${limit} requests/minute`);
      
      // Implement exponential backoff
      await new Promise(resolve => 
        setTimeout(resolve, retryAfter * 1000)
      );
      
      // Retry request
      const transactions = await monei.transactions.getAll();
    }
  }
  ```

  ```python Python theme={null}
  import time
  from monei.exceptions import RateLimitError

  try:
      transactions = monei.transactions.get_all()
  except RateLimitError as error:
      print('Rate limit exceeded')
      
      retry_after = error.retry_after
      print(f'Retry after {retry_after} seconds')
      
      # Wait and retry
      time.sleep(retry_after)
      transactions = monei.transactions.get_all()
  ```
</CodeGroup>

**Error Code:** `RATE_LIMIT_EXCEEDED`

**Rate Limits:**

* Free tier: 100 requests/minute
* Paid plans: 1,000 requests/minute
* Burst allowance: 2x limit for 10 seconds

**Solutions:**

* Implement exponential backoff
* Cache frequently accessed data
* Use webhooks instead of polling
* Upgrade plan for higher limits

***

### Network Errors

<CodeGroup>
  ```javascript Node.js theme={null}
  try {
    const tx = await monei.evm.sendToken({...});
  } catch (error) {
    if (error.code === 'NETWORK_ERROR') {
      console.error('Blockchain network error');
      console.error('Network:', error.details.network);
      console.error('Reason:', error.details.reason);
      
      // Common network errors:
      // - RPC timeout
      // - Network congestion
      // - Gas price too low
      // - Nonce mismatch
      
      // Retry with exponential backoff
      const maxRetries = 3;
      for (let i = 0; i < maxRetries; i++) {
        try {
          const tx = await monei.evm.sendToken({...});
          break; // Success
        } catch (retryError) {
          if (i === maxRetries - 1) throw retryError;
          await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
        }
      }
    }
  }
  ```

  ```python Python theme={null}
  import time
  from monei.exceptions import NetworkError

  def send_with_retry(func, max_retries=3):
      for i in range(max_retries):
          try:
              return func()
          except NetworkError as error:
              if i == max_retries - 1:
                  raise
              wait = 2 ** i
              print(f'Network error, retrying in {wait}s...')
              time.sleep(wait)

  # Usage
  tx = send_with_retry(lambda: monei.evm.send_token(...))
  ```
</CodeGroup>

**Error Code:** `NETWORK_ERROR`

**Common Causes:**

* RPC endpoint timeout
* Network congestion
* Gas price estimation failure
* Blockchain node issues

**Solutions:**

* Implement retry with exponential backoff
* Increase gas price
* Try different RPC endpoint
* Wait for network congestion to clear

***

## Error Handling Patterns

### Try-Catch Pattern

<CodeGroup>
  ```javascript Node.js theme={null}
  async function safeTransfer(params) {
    try {
      const tx = await monei.evm.sendToken(params);
      return { success: true, data: tx };
    } catch (error) {
      console.error('Transaction failed:', error.message);
      
      // Log error details
      console.error('Status:', error.statusCode);
      console.error('Code:', error.code);
      console.error('Details:', error.details);
      
      // Return error info
      return {
        success: false,
        error: {
          message: error.message,
          code: error.code,
          recoverable: isRecoverable(error)
        }
      };
    }
  }

  function isRecoverable(error) {
    const recoverableCodes = [
      'NETWORK_ERROR',
      'RATE_LIMIT_EXCEEDED',
      'NETWORK_CONGESTION'
    ];
    return recoverableCodes.includes(error.code);
  }
  ```

  ```python Python theme={null}
  def safe_transfer(params):
      try:
          tx = monei.evm.send_token(**params)
          return {'success': True, 'data': tx}
      except Exception as error:
          print(f'Transaction failed: {error.message}')
          
          # Log error
          print(f'Status: {error.status_code}')
          print(f'Code: {error.code}')
          
          return {
              'success': False,
              'error': {
                  'message': error.message,
                  'code': error.code,
                  'recoverable': is_recoverable(error)
              }
          }

  def is_recoverable(error):
      recoverable = ['NETWORK_ERROR', 'RATE_LIMIT_EXCEEDED']
      return error.code in recoverable
  ```
</CodeGroup>

### Retry with Exponential Backoff

<CodeGroup>
  ```javascript Node.js theme={null}
  async function retryWithBackoff(
    fn,
    maxRetries = 3,
    baseDelay = 1000
  ) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        return await fn();
      } catch (error) {
        const isLastAttempt = attempt === maxRetries - 1;
        const isRetryable = isRecoverable(error);
        
        if (isLastAttempt || !isRetryable) {
          throw error;
        }
        
        const delay = baseDelay * Math.pow(2, attempt);
        console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
        
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  // Usage
  const tx = await retryWithBackoff(
    () => monei.evm.sendToken({...}),
    3,
    1000
  );
  ```

  ```python Python theme={null}
  import time

  def retry_with_backoff(func, max_retries=3, base_delay=1):
      for attempt in range(max_retries):
          try:
              return func()
          except Exception as error:
              is_last = attempt == max_retries - 1
              is_retryable = is_recoverable(error)
              
              if is_last or not is_retryable:
                  raise
              
              delay = base_delay * (2 ** attempt)
              print(f'Retry {attempt + 1} after {delay}s')
              time.sleep(delay)

  # Usage
  tx = retry_with_backoff(
      lambda: monei.evm.send_token(...),
      max_retries=3
  )
  ```
</CodeGroup>

### Circuit Breaker Pattern

<CodeGroup>
  ```javascript Node.js theme={null}
  class CircuitBreaker {
    constructor(threshold = 5, timeout = 60000) {
      this.failureCount = 0;
      this.threshold = threshold;
      this.timeout = timeout;
      this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
      this.nextAttempt = Date.now();
    }
    
    async execute(fn) {
      if (this.state === 'OPEN') {
        if (Date.now() < this.nextAttempt) {
          throw new Error('Circuit breaker is OPEN');
        }
        this.state = 'HALF_OPEN';
      }
      
      try {
        const result = await fn();
        this.onSuccess();
        return result;
      } catch (error) {
        this.onFailure();
        throw error;
      }
    }
    
    onSuccess() {
      this.failureCount = 0;
      this.state = 'CLOSED';
    }
    
    onFailure() {
      this.failureCount++;
      if (this.failureCount >= this.threshold) {
        this.state = 'OPEN';
        this.nextAttempt = Date.now() + this.timeout;
      }
    }
  }

  // Usage
  const breaker = new CircuitBreaker(5, 60000);

  try {
    const tx = await breaker.execute(
      () => monei.evm.sendToken({...})
    );
  } catch (error) {
    console.error('Circuit breaker prevented call or call failed');
  }
  ```
</CodeGroup>

***

## Validation Before Requests

Validate inputs before making API calls to catch errors early:

<CodeGroup>
  ```javascript Node.js theme={null}
  function validateTransferParams(params) {
    const errors = [];
    
    // Validate address
    if (!params.to || params.to.length !== 42) {
      errors.push('Invalid recipient address');
    }
    
    // Validate amount
    if (!params.amount || parseFloat(params.amount) <= 0) {
      errors.push('Amount must be greater than 0');
    }
    
    // Validate chainId
    const validChains = [1, 56, 137, 8453, 42161, 10];
    if (!validChains.includes(params.chainId)) {
      errors.push('Invalid chain ID');
    }
    
    if (errors.length > 0) {
      throw new Error(`Validation failed: ${errors.join(', ')}`);
    }
    
    return true;
  }

  // Use before API call
  validateTransferParams({
    to: '0xRecipientAddress',
    amount: '100',
    chainId: 56
  });

  const tx = await monei.evm.sendToken({...});
  ```

  ```python Python theme={null}
  def validate_transfer_params(params):
      errors = []
      
      # Validate address
      if not params.get('to') or len(params['to']) != 42:
          errors.append('Invalid recipient address')
      
      # Validate amount
      if not params.get('amount') or float(params['amount']) <= 0:
          errors.append('Amount must be greater than 0')
      
      # Validate chainId
      valid_chains = [1, 56, 137, 8453, 42161, 10]
      if params.get('chain_id') not in valid_chains:
          errors.append('Invalid chain ID')
      
      if errors:
          raise ValueError(f"Validation failed: {', '.join(errors)}")
      
      return True

  # Use before API call
  validate_transfer_params({
      'to': '0xRecipientAddress',
      'amount': '100',
      'chain_id': 56
  })

  tx = monei.evm.send_token(...)
  ```
</CodeGroup>

***

## Debugging Tips

<AccordionGroup>
  <Accordion icon="bug" title="Enable Debug Logging">
    **Node.js:**

    ```javascript theme={null}
    const monei = new MoneiSDK({
      apiKey: process.env.MONEI_API_KEY,
      debug: true // Enables detailed logging
    });
    ```

    **Python:**

    ```python theme={null}
    import logging
    logging.basicConfig(level=logging.DEBUG)

    monei = MoneiClient(
        api_key=os.getenv('MONEI_API_KEY'),
        debug=True
    )
    ```
  </Accordion>

  <Accordion icon="terminal" title="Inspect API Responses">
    **Log full error objects:**

    ```javascript theme={null}
    try {
      await monei.evm.sendToken({...});
    } catch (error) {
      console.error('Full error object:', JSON.stringify(error, null, 2));
      console.error('Stack trace:', error.stack);
    }
    ```

    **Check response headers:**

    ```javascript theme={null}
    console.log('Rate limit remaining:', error.headers['x-ratelimit-remaining']);
    console.log('Request ID:', error.headers['x-request-id']);
    ```
  </Accordion>

  <Accordion icon="network-wired" title="Test in Sandbox">
    Always test error scenarios in sandbox:

    ```javascript theme={null}
    const monei = new MoneiSDK({
      apiKey: process.env.MONEI_SANDBOX_KEY,
      environment: 'sandbox'
    });

    // Test insufficient balance
    // Test invalid addresses
    // Test rate limits
    ```
  </Accordion>

  <Accordion icon="life-ring" title="Contact Support">
    When contacting support, provide:

    * Request ID (from error headers)
    * Timestamp of error
    * Full error message
    * Request parameters (sanitized)
    * Expected vs. actual behavior

    ```javascript theme={null}
    console.error({
      requestId: error.headers['x-request-id'],
      timestamp: error.timestamp,
      path: error.path,
      message: error.message
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Validate Early" icon="check">
    Validate all inputs before making API calls to catch errors early
  </Card>

  <Card title="Handle Gracefully" icon="hands-holding">
    Show user-friendly error messages, not raw API errors
  </Card>

  <Card title="Implement Retries" icon="rotate">
    Use exponential backoff for transient errors
  </Card>

  <Card title="Log Everything" icon="file-lines">
    Log errors with full context for debugging
  </Card>
</CardGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Testing" icon="flask" href="/testing/sandbox">
    Test error scenarios in sandbox environment
  </Card>

  <Card title="Webhooks" icon="webhook" href="/security/webhooks">
    Get real-time error notifications
  </Card>

  <Card title="Support" icon="life-ring" href="/support/support">
    Contact support for help with errors
  </Card>

  <Card title="FAQ" icon="circle-question" href="/support/faq">
    Common questions and solutions
  </Card>
</CardGroup>
