Skip to main content

Overview

Proper error handling ensures your application remains resilient and provides a great user experience even when issues occur. This guide covers common errors, best practices, and implementation patterns.

HTTP Status Codes

The Fraudiant API uses standard HTTP status codes to indicate success or failure:
Status CodeMeaningDescription
200SuccessRequest completed successfully
400Bad RequestInvalid input (malformed email, invalid domain)
401UnauthorizedMissing or invalid API key
403ForbiddenFeature requires Pro account or insufficient permissions
404Not FoundResource not found (e.g., domain not in blocklist)
422Unprocessable EntityValidation failed (e.g., duplicate blocklist entry)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableTemporary service outage

Common Error Responses

Invalid Email (400)

Returned when the email address format is invalid:
{
  "status": 400,
  "error": "The email address is invalid."
}
Cause: Malformed email address (missing @, invalid characters, etc.) Solution:
function validateEmailFormat(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!regex.test(email)) {
    return { valid: false, error: 'Invalid email format' };
  }
  return { valid: true };
}

Unauthorized (401)

Returned when API key is missing, invalid, or expired:
{
  "status": 401,
  "error": "Unauthorized. Please provide a valid API key."
}
Common causes:
  • Missing Authorization header
  • Invalid API key format
  • Expired or revoked API key
  • Typo in API key
Solution:
async function validateWithAuth(email) {
  const apiKey = process.env.FRAUDIANT_API_KEY;

  if (!apiKey) {
    throw new Error('FRAUDIANT_API_KEY environment variable not set');
  }

  try {
    const response = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${apiKey}`
        }
      }
    );

    if (response.status === 401) {
      console.error('Invalid API key. Please check your credentials.');
      // Alert admin or rotate key
      throw new Error('Authentication failed');
    }

    return response.json();
  } catch (error) {
    console.error('Authentication error:', error);
    throw error;
  }
}

Pro Feature Required (403)

Returned when attempting to use Pro-only features (like blocklist management):
{
  "status": 403,
  "error": "This feature requires a Pro account."
}
Solution:
async function addToBlocklist(domain) {
  try {
    const response = await fetch(
      'https://api.fraudiant.com/blocklist',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ domain })
      }
    );

    if (response.status === 403) {
      return {
        success: false,
        error: 'Pro account required. Upgrade at https://app.fraudiant.com/upgrade'
      };
    }

    return { success: true };
  } catch (error) {
    console.error('Blocklist error:', error);
    return { success: false, error: error.message };
  }
}

Rate Limit Exceeded (429)

Returned when you exceed your rate limit:
{
  "status": 429,
  "error": "Too many requests",
  "retry_after": 30
}
Solution with exponential backoff:
async function validateWithRetry(email, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(
        `https://api.fraudiant.com/email/${email}`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
          }
        }
      );

      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
        console.log(`Rate limited. Retrying after ${retryAfter}s...`);

        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue; // Retry
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();

    } catch (error) {
      if (attempt === maxRetries - 1) {
        console.error('Max retries exceeded:', error);
        throw error;
      }

      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Service Unavailable (503)

Returned during temporary service outages:
{
  "status": 503,
  "error": "Service temporarily unavailable. Please try again later."
}
Solution: Fail Open Strategy
async function validateEmailSafe(email) {
  try {
    const response = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
        },
        timeout: 5000 // 5 second timeout
      }
    );

    if (response.status === 503) {
      console.warn('Fraudiant service unavailable. Failing open.');
      return {
        valid: true,
        failedOpen: true,
        message: 'Validation service temporarily unavailable'
      };
    }

    const validation = await response.json();

    return {
      valid: !validation.disposable && !validation.spam && validation.mx,
      data: validation
    };

  } catch (error) {
    console.error('Service error:', error);
    // Fail open - don't block users
    return {
      valid: true,
      failedOpen: true,
      error: error.message
    };
  }
}

Network & Timeout Errors

Timeout Handling

async function validateWithTimeout(email, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
        },
        signal: controller.signal
      }
    );

    clearTimeout(timeoutId);
    return await response.json();

  } catch (error) {
    clearTimeout(timeoutId);

    if (error.name === 'AbortError') {
      console.error('Request timeout after', timeoutMs, 'ms');
      // Fail open
      return { disposable: false, spam: false, mx: true, timeout: true };
    }

    throw error;
  }
}

Connection Errors

async function validateWithConnectionHandling(email) {
  try {
    const response = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
        }
      }
    );

    return await response.json();

  } catch (error) {
    // Network errors
    if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
      console.error('Cannot reach Fraudiant API. Check network connection.');
      return { valid: true, networkError: true };
    }

    // DNS errors
    if (error.code === 'EAI_AGAIN') {
      console.error('DNS resolution failed. Temporary network issue.');
      return { valid: true, dnsError: true };
    }

    // SSL/TLS errors
    if (error.code === 'CERT_HAS_EXPIRED') {
      console.error('SSL certificate error');
      return { valid: true, sslError: true };
    }

    throw error;
  }
}

Error Logging & Monitoring

Structured Error Logging

class ErrorLogger {
  static log(context, error, metadata = {}) {
    const logEntry = {
      timestamp: new Date().toISOString(),
      context,
      error: {
        message: error.message,
        stack: error.stack,
        code: error.code
      },
      metadata,
      severity: this.getSeverity(error)
    };

    console.error(JSON.stringify(logEntry));

    // Send to monitoring service (e.g., Sentry, DataDog)
    // this.sendToMonitoring(logEntry);
  }

  static getSeverity(error) {
    if (error.code === 401) return 'critical'; // Auth failure
    if (error.code === 429) return 'warning';  // Rate limit
    if (error.code === 503) return 'warning';  // Service down
    return 'error';
  }
}

// Usage
async function validateWithLogging(email) {
  try {
    const validation = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
        }
      }
    ).then(r => r.json());

    return validation;

  } catch (error) {
    ErrorLogger.log('email_validation', error, { email: email.split('@')[1] });

    // Fail open
    return { valid: true, error: true };
  }
}

Circuit Breaker Pattern

Prevent cascading failures by implementing a circuit breaker:
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;
      console.error('Circuit breaker opened');
    }
  }
}

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

async function validateWithCircuitBreaker(email) {
  try {
    return await breaker.execute(async () => {
      const response = await fetch(
        `https://api.fraudiant.com/email/${email}`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
          }
        }
      );

      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    });
  } catch (error) {
    console.error('Validation failed:', error.message);
    // Fail open when circuit is open
    return { valid: true, circuitOpen: true };
  }
}

Error Recovery Strategies

Graceful Degradation

async function validateWithFallback(email) {
  try {
    // Primary: Full validation
    const validation = await fetch(
      `https://api.fraudiant.com/email/${email}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
        },
        timeout: 3000
      }
    ).then(r => r.json());

    return {
      valid: !validation.disposable && !validation.spam && validation.mx,
      quality: 'full',
      data: validation
    };

  } catch (error) {
    console.warn('Full validation failed, using fallback');

    // Fallback: Basic format check only
    const isValidFormat = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

    return {
      valid: isValidFormat,
      quality: 'basic',
      fallback: true,
      error: error.message
    };
  }
}

Queue Failed Requests

class ValidationQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }

  async add(email) {
    return new Promise((resolve, reject) => {
      this.queue.push({ email, resolve, reject, retries: 0 });
      this.process();
    });
  }

  async process() {
    if (this.processing || this.queue.length === 0) return;

    this.processing = true;

    while (this.queue.length > 0) {
      const item = this.queue[0];

      try {
        const validation = await fetch(
          `https://api.fraudiant.com/email/${item.email}`,
          {
            headers: {
              'Authorization': `Bearer ${process.env.FRAUDIANT_API_KEY}`
            }
          }
        ).then(r => r.json());

        this.queue.shift(); // Remove from queue
        item.resolve(validation);

      } catch (error) {
        item.retries++;

        if (item.retries >= 3) {
          this.queue.shift(); // Remove after max retries
          item.reject(error);
        } else {
          // Move to back of queue
          this.queue.push(this.queue.shift());
          await new Promise(r => setTimeout(r, 1000 * item.retries));
        }
      }
    }

    this.processing = false;
  }
}

Testing Error Scenarios

Mock Error Responses

// test-helpers/fraudiant-mock.js
class FraudiantMock {
  constructor(mode = 'success') {
    this.mode = mode;
  }

  async validate(email) {
    switch (this.mode) {
      case 'rate_limit':
        throw { status: 429, error: 'Too many requests' };

      case 'auth_error':
        throw { status: 401, error: 'Unauthorized' };

      case 'service_down':
        throw { status: 503, error: 'Service unavailable' };

      case 'timeout':
        await new Promise(r => setTimeout(r, 10000)); // Simulate timeout
        throw new Error('Timeout');

      case 'network_error':
        throw { code: 'ECONNREFUSED', message: 'Connection refused' };

      default:
        return {
          disposable: email.includes('temp'),
          spam: false,
          mx: true
        };
    }
  }
}

// Usage in tests
describe('Error Handling', () => {
  it('should handle rate limit errors', async () => {
    const mock = new FraudiantMock('rate_limit');
    const result = await validateWithRetry('[email protected]', mock);
    expect(result.valid).toBe(true);
    expect(result.failedOpen).toBe(true);
  });
});

Best Practices Summary

Set reasonable timeouts (3-5 seconds) to prevent hanging requests.
When errors occur, allow users to proceed rather than blocking them completely.
Include request metadata, timestamps, and error details for debugging.
Use exponential backoff for transient failures like rate limits and network issues.
Track error patterns to identify systemic issues early.
Use mocks to test how your application handles various error conditions.

Next Steps