Error Handling

Understanding API errors, status codes, and how to handle them gracefully in your applications.

Error Response Format

All API errors return a consistent JSON structure:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "status": 400,
    "details": {
      // Additional context (optional)
    }
  }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
statusnumberHTTP status code
detailsobjectAdditional error context (optional)

HTTP Status Codes

Client Errors (4xx)

StatusNameDescription
400Bad RequestInvalid request parameters or body
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient credits or permissions
404Not FoundResource not found
429Too Many RequestsRate limit exceeded

Server Errors (5xx)

StatusNameDescription
500Internal Server ErrorUnexpected server error - retry the request
502Bad GatewayUpstream service error - retry after delay
503Service UnavailableTemporary service outage - retry after delay
504Gateway TimeoutRequest timed out - retry with smaller batch

Error Codes

Complete list of error codes and their meanings:

Authentication Errors

CodeHTTPDescriptionSolution
MISSING_API_KEY401No API key providedAdd Authorization header
INVALID_API_KEY401API key is invalid or revokedCheck key in dashboard
API_KEY_EXPIRED401API key has expiredCreate a new API key

Validation Errors

CodeHTTPDescriptionSolution
MISSING_EMAIL400Email parameter missingInclude email in request body
INVALID_EMAIL400Email format is invalidCheck email format
INVALID_JSON400Request body is not valid JSONFix JSON syntax
BULK_LIMIT_EXCEEDED400Too many emails in bulk requestLimit to 10,000 per request

Quota & Rate Limit Errors

CodeHTTPDescriptionSolution
INSUFFICIENT_CREDITS403No verification credits remainingPurchase more credits
RATE_LIMITED429Too many requestsWait and retry with backoff
DAILY_LIMIT_REACHED429Daily request limit reachedWait until reset or upgrade plan

Rate Limiting

When you hit a rate limit, the API returns a 429 status with additional headers:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000060
Retry-After: 60

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Please slow down your requests.",
    "status": 429,
    "details": {
      "limit": 10,
      "remaining": 0,
      "reset": 1640000060,
      "retryAfter": 60
    }
  }
}
HeaderDescription
X-RateLimit-LimitMaximum requests per second
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when limit resets
Retry-AfterSeconds to wait before retrying

Handling Errors in Code

JavaScript

async function verifyEmail(email) {
  try {
    const response = await fetch('https://validmail.io/api/v1/verify', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.VALIDMAIL_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
    });

    if (!response.ok) {
      const errorData = await response.json();
      const { error } = errorData;

      switch (error.code) {
        case 'RATE_LIMITED':
          const retryAfter = error.details?.retryAfter || 60;
          console.log(`Rate limited. Retry after ${retryAfter}s`);
          await sleep(retryAfter * 1000);
          return verifyEmail(email); // Retry

        case 'INSUFFICIENT_CREDITS':
          throw new Error('Please purchase more credits');

        case 'INVALID_EMAIL':
          return { status: 'invalid', message: 'Invalid email format' };

        default:
          throw new Error(error.message);
      }
    }

    return response.json();
  } catch (err) {
    if (err.name === 'TypeError') {
      // Network error - retry with backoff
      console.log('Network error, retrying...');
      await sleep(1000);
      return verifyEmail(email);
    }
    throw err;
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Python

import requests
import time
from requests.exceptions import RequestException

class ValidMailError(Exception):
    def __init__(self, code, message, status):
        self.code = code
        self.message = message
        self.status = status
        super().__init__(message)

def verify_email(email, retries=3):
    for attempt in range(retries):
        try:
            response = requests.post(
                'https://validmail.io/api/v1/verify',
                headers={
                    'Authorization': f'Bearer {os.environ["VALIDMAIL_API_KEY"]}',
                    'Content-Type': 'application/json',
                },
                json={'email': email},
                timeout=30
            )

            if response.ok:
                return response.json()

            error = response.json().get('error', {})

            if error.get('code') == 'RATE_LIMITED':
                retry_after = error.get('details', {}).get('retryAfter', 60)
                time.sleep(retry_after)
                continue

            raise ValidMailError(
                error.get('code'),
                error.get('message'),
                error.get('status')
            )

        except RequestException as e:
            if attempt < retries - 1:
                time.sleep(2 ** attempt)  # Exponential backoff
                continue
            raise

    raise Exception('Max retries exceeded')

Best Practices

Implement exponential backoff

When retrying failed requests, wait progressively longer between attempts (1s, 2s, 4s, 8s...).

Handle rate limits proactively

Check X-RateLimit-Remaining headers and slow down before hitting limits.

Log errors with context

Include the error code, message, and request details for easier debugging.

Don't retry 4xx errors

Client errors (except 429) won't succeed on retry. Fix the request instead.

Set reasonable timeouts

Use 30-60 second timeouts for verification requests, as some domains may be slow to respond.