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
| Status | Error | Description |
|---|---|---|
| 200 | Success | Request processed successfully |
| 400 | Bad Request | Invalid request format or parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server error |
| 503 | Service Unavailable | Service temporarily unavailable |
Common Errors
400 Bad Request
Cause: Invalid request format or missing required fieldsMissing Email Field
{
"error": "Bad Request",
"message": "Email field is required"
}
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"
}
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"
}
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 keyMissing API Key Header
{
"error": "Unauthorized",
"message": "X-API-Key header is required"
}
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"
}
// 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"
}
429 Too Many Requests
Cause: Rate limit exceeded (10,000 requests/minute per API key or 1,000 requests/minute per IP){
"error": "Rate limit exceeded",
"message": "You have exceeded the rate limit of 10000 requests per minute",
"retry_after": 60
}
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"
}
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."
}
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"
}
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"
}
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"
}
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
Rate Limits
Understand rate limiting strategies
API Integration
Full integration patterns