Skip to main content

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:
{
  "statusCode": 400,
  "message": "Insufficient balance for transaction",
  "error": {
    "details": {
        "required": 150.50,
        "available": 100.00,
        "currency": "USDT"
    }
  }
}

Response Fields

FieldTypeDescription
statusCodenumberHTTP status code (400, 401, 403, 404, 429, 500)
messagestringHuman-readable error description
errorstringError category
detailsobjectAdditional error context (optional)

HTTP Status Codes

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

Common Error Types

Authentication Errors

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
  }
}
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

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);
  }
}
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

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
    }
  }
}
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

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');
  }
}
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

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();
  }
}
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

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));
      }
    }
  }
}
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

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);
}

Retry with Exponential Backoff

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
);

Circuit Breaker Pattern

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');
}

Validation Before Requests

Validate inputs before making API calls to catch errors early:
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({...});

Debugging Tips

Node.js:
const monei = new MoneiSDK({
  apiKey: process.env.MONEI_API_KEY,
  debug: true // Enables detailed logging
});
Python:
import logging
logging.basicConfig(level=logging.DEBUG)

monei = MoneiClient(
    api_key=os.getenv('MONEI_API_KEY'),
    debug=True
)
Log full error objects:
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:
console.log('Rate limit remaining:', error.headers['x-ratelimit-remaining']);
console.log('Request ID:', error.headers['x-request-id']);
Always test error scenarios in sandbox:
const monei = new MoneiSDK({
  apiKey: process.env.MONEI_SANDBOX_KEY,
  environment: 'sandbox'
});

// Test insufficient balance
// Test invalid addresses
// Test rate limits
When contacting support, provide:
  • Request ID (from error headers)
  • Timestamp of error
  • Full error message
  • Request parameters (sanitized)
  • Expected vs. actual behavior
console.error({
  requestId: error.headers['x-request-id'],
  timestamp: error.timestamp,
  path: error.path,
  message: error.message
});

Best Practices

Validate Early

Validate all inputs before making API calls to catch errors early

Handle Gracefully

Show user-friendly error messages, not raw API errors

Implement Retries

Use exponential backoff for transient errors

Log Everything

Log errors with full context for debugging

Next Steps

Testing

Test error scenarios in sandbox environment

Webhooks

Get real-time error notifications

Support

Contact support for help with errors

FAQ

Common questions and solutions