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)
}
}
}| Field | Type | Description |
|---|---|---|
| code | string | Machine-readable error code |
| message | string | Human-readable description |
| status | number | HTTP status code |
| details | object | Additional error context (optional) |
HTTP Status Codes
Client Errors (4xx)
| Status | Name | Description |
|---|---|---|
| 400 | Bad Request | Invalid request parameters or body |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Insufficient credits or permissions |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
Server Errors (5xx)
| Status | Name | Description |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error - retry the request |
| 502 | Bad Gateway | Upstream service error - retry after delay |
| 503 | Service Unavailable | Temporary service outage - retry after delay |
| 504 | Gateway Timeout | Request timed out - retry with smaller batch |
Error Codes
Complete list of error codes and their meanings:
Authentication Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
| MISSING_API_KEY | 401 | No API key provided | Add Authorization header |
| INVALID_API_KEY | 401 | API key is invalid or revoked | Check key in dashboard |
| API_KEY_EXPIRED | 401 | API key has expired | Create a new API key |
Validation Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
| MISSING_EMAIL | 400 | Email parameter missing | Include email in request body |
| INVALID_EMAIL | 400 | Email format is invalid | Check email format |
| INVALID_JSON | 400 | Request body is not valid JSON | Fix JSON syntax |
| BULK_LIMIT_EXCEEDED | 400 | Too many emails in bulk request | Limit to 10,000 per request |
Quota & Rate Limit Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
| INSUFFICIENT_CREDITS | 403 | No verification credits remaining | Purchase more credits |
| RATE_LIMITED | 429 | Too many requests | Wait and retry with backoff |
| DAILY_LIMIT_REACHED | 429 | Daily request limit reached | Wait 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
}
}
}| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per second |
| X-RateLimit-Remaining | Remaining requests in current window |
| X-RateLimit-Reset | Unix timestamp when limit resets |
| Retry-After | Seconds 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.