Skip to main content

Error Handling Guide

Learn how to properly handle errors from the GTMAPIs validation API.

Error Response Format

All errors return an appropriate HTTP status code with JSON body:
{
  "error": "Error Type",
  "message": "Detailed error message"
}

HTTP Status Codes

StatusErrorDescription
200SuccessRequest processed successfully
400Bad RequestInvalid request format or parameters
401UnauthorizedMissing or invalid API key
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableService temporarily unavailable

Common Errors

400 Bad Request

Cause: Invalid request format or missing required fields

Missing Email Field

{
  "error": "Bad Request",
  "message": "Email field is required"
}
Solution: Include email field in request body
// ❌ Wrong
fetch('https://api.gtmapis.com/v1/validate', {
  body: JSON.stringify({})
});

// ✅ Correct
fetch('https://api.gtmapis.com/v1/validate', {
  body: JSON.stringify({ email: 'john@company.com' })
});

Invalid Email Format

{
  "error": "Bad Request",
  "message": "Invalid email format"
}
Solution: Validate email format before sending
function isValidEmailFormat(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

if (!isValidEmailFormat(email)) {
  throw new Error('Invalid email format');
}

Too Many Emails in Bulk Request

{
  "error": "Bad Request",
  "message": "Maximum 100 emails per request. Received: 150"
}
Solution: Split into batches of 100
function chunkArray(array, size) {
  const chunks = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

const batches = chunkArray(emails, 100);
for (const batch of batches) {
  await validateBulk(batch);
}

401 Unauthorized

Cause: Missing or invalid API key

Missing API Key Header

{
  "error": "Unauthorized",
  "message": "X-API-Key header is required"
}
Solution: Always include X-API-Key header
// ❌ Wrong
fetch('https://api.gtmapis.com/v1/validate', {
  headers: {
    'Content-Type': 'application/json'
  }
});

// ✅ Correct
fetch('https://api.gtmapis.com/v1/validate', {
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': process.env.GTMAPIS_API_KEY
  }
});

Invalid API Key

{
  "error": "Unauthorized",
  "message": "Invalid API key"
}
Solution: Verify API key format and validity
// Check API key format
if (!apiKey || !apiKey.match(/^gtm_(test|live)_[a-f0-9]{32}$/)) {
  throw new Error('Invalid API key format');
}

Revoked API Key

{
  "error": "Unauthorized",
  "message": "API key has been revoked"
}
Solution: Generate a new API key from the dashboard

429 Too Many Requests

Cause: Rate limit exceeded (1000 requests/minute)
{
  "error": "Rate limit exceeded",
  "message": "You have exceeded the rate limit of 1000 requests per minute",
  "retry_after": 60
}
Solution: Implement retry logic with exponential backoff
async function validateWithRetry(email, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.gtmapis.com/v1/validate', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-API-Key': process.env.GTMAPIS_API_KEY
        },
        body: JSON.stringify({ email })
      });

      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
        console.log(`Rate limited. Waiting ${retryAfter}s...`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }

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

      return await response.json();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

500 Internal Server Error

Cause: Unexpected server error
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred"
}
Solution: Retry request or contact support
async function validateWithErrorHandling(email) {
  try {
    return await validateEmail(email);
  } catch (error) {
    if (error.status === 500) {
      // Log for investigation
      console.error('Server error:', error);

      // Retry once after delay
      await new Promise(resolve => setTimeout(resolve, 5000));
      return await validateEmail(email);
    }
    throw error;
  }
}

503 Service Unavailable

Cause: Service temporarily unavailable (maintenance or overload)
{
  "error": "Service Unavailable",
  "message": "Service temporarily unavailable. Please try again later."
}
Solution: Implement 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();

async function validateWithCircuitBreaker(email) {
  return await breaker.execute(() => validateEmail(email));
}

Validation Result Errors

Even with HTTP 200, emails can have validation issues:

Invalid Email

{
  "email": "notreal@fakeemail123.com",
  "result": "invalid",
  "reason": "Domain has no MX records",
  "validation_layer": "dns"
}
Handling:
if (result.result === 'invalid') {
  console.log(`Email ${result.email} is invalid: ${result.reason}`);
  // Remove from list
  removeFromList(result.email);
}

Risky Email

{
  "email": "anyone@catchall.com",
  "result": "risky",
  "reason": "Catch-all domain detected"
}
Handling:
if (result.result === 'risky') {
  console.log(`Email ${result.email} is risky: ${result.reason}`);
  // Mark as low priority or exclude
  markAsLowPriority(result.email);
}

Unknown Verification

{
  "email": "john@restrictive.com",
  "result": "unknown",
  "reason": "SMTP verification not permitted"
}
Handling:
if (result.result === 'unknown') {
  console.log(`Cannot verify ${result.email}: ${result.reason}`);
  // Test with small batch first
  addToTestBatch(result.email);
}

Error Handling Patterns

Comprehensive Error Handler

async function validateEmailSafely(email) {
  try {
    const response = await fetch('https://api.gtmapis.com/v1/validate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.GTMAPIS_API_KEY
      },
      body: JSON.stringify({ email })
    });

    // Handle HTTP errors
    if (!response.ok) {
      switch (response.status) {
        case 400:
          throw new Error('Invalid request format');
        case 401:
          throw new Error('Invalid API key - check your credentials');
        case 429:
          const retryAfter = response.headers.get('Retry-After') || 60;
          throw new Error(`Rate limited - retry after ${retryAfter}s`);
        case 500:
          throw new Error('Server error - please retry');
        case 503:
          throw new Error('Service unavailable - try again later');
        default:
          throw new Error(`HTTP ${response.status}`);
      }
    }

    const result = await response.json();

    // Handle validation results
    switch (result.result) {
      case 'valid':
        return { status: 'success', data: result };

      case 'valid_role_based':
        return {
          status: 'warning',
          message: 'Role-based email (info@, support@)',
          data: result
        };

      case 'risky':
        return {
          status: 'warning',
          message: 'Catch-all domain - cannot verify mailbox',
          data: result
        };

      case 'invalid':
        return {
          status: 'error',
          message: result.reason || 'Invalid email',
          data: result
        };

      case 'unknown':
        return {
          status: 'warning',
          message: 'Cannot verify - server restrictions',
          data: result
        };

      default:
        return {
          status: 'error',
          message: 'Unexpected result type',
          data: result
        };
    }
  } catch (error) {
    // Network or parsing errors
    console.error('Validation error:', error);
    return {
      status: 'error',
      message: error.message,
      data: null
    };
  }
}

// Usage
const result = await validateEmailSafely('john@company.com');

if (result.status === 'success') {
  // Email is valid
  console.log('Valid email:', result.data.email);
} else if (result.status === 'warning') {
  // Email has issues but may be usable
  console.warn(result.message, result.data);
} else {
  // Email is invalid or error occurred
  console.error(result.message);
}

Bulk Validation Error Handling

async function validateBulkWithErrorHandling(emails) {
  const results = [];
  const errors = [];

  for (let i = 0; i < emails.length; i += 100) {
    const batch = emails.slice(i, i + 100);

    try {
      const response = await fetch('https://api.gtmapis.com/v1/validate/bulk', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-API-Key': process.env.GTMAPIS_API_KEY
        },
        body: JSON.stringify({ emails: batch })
      });

      if (!response.ok) {
        // Log error but continue with next batch
        errors.push({
          batch: i / 100 + 1,
          emails: batch,
          error: `HTTP ${response.status}`
        });
        continue;
      }

      const batchResults = await response.json();
      results.push(...batchResults.results);

    } catch (error) {
      // Network error - log and continue
      errors.push({
        batch: i / 100 + 1,
        emails: batch,
        error: error.message
      });
    }

    // Rate limit delay
    await new Promise(resolve => setTimeout(resolve, 500));
  }

  return {
    results,
    errors,
    summary: {
      total: emails.length,
      validated: results.length,
      failed: errors.reduce((sum, e) => sum + e.emails.length, 0)
    }
  };
}

Monitoring and Logging

Log Errors for Analysis

function logValidationError(error, context) {
  console.error('Validation Error:', {
    timestamp: new Date().toISOString(),
    error: error.message,
    stack: error.stack,
    context: {
      email: context.email,
      apiKey: context.apiKey?.substring(0, 15) + '...',  // Partial key only
      endpoint: context.endpoint
    }
  });

  // Send to error tracking service
  if (process.env.SENTRY_DSN) {
    Sentry.captureException(error, {
      contexts: { validation: context }
    });
  }
}

Track Error Rates

class ErrorTracker {
  constructor() {
    this.errors = new Map();
  }

  track(errorType) {
    const count = this.errors.get(errorType) || 0;
    this.errors.set(errorType, count + 1);
  }

  getStats() {
    return Object.fromEntries(this.errors);
  }

  reset() {
    this.errors.clear();
  }
}

const errorTracker = new ErrorTracker();

// Track errors
try {
  await validateEmail(email);
} catch (error) {
  if (error.status === 429) {
    errorTracker.track('rate_limit');
  } else if (error.status === 500) {
    errorTracker.track('server_error');
  }
  throw error;
}

// Review stats periodically
setInterval(() => {
  console.log('Error stats:', errorTracker.getStats());
  errorTracker.reset();
}, 60000);

Next Steps