SERP API Error Handling: Complete Guide to Robust Integration
Building reliable applications with SERP APIs requires more than just making successful requests—you need robust error handling to deal with network issues, rate limits, timeouts, and unexpected responses. This comprehensive guide covers everything you need to know about handling errors gracefully in your SERP API integrations.
Why Error Handling Matters
Real-world statistics:
- Network failures occur in 0.5-2% of API requests
- Timeout issues affect 1-3% of requests during peak hours
- Proper error handling can improve application uptime by 99.9%
Without proper error handling:
- �?Application crashes on network failures
- �?Users see cryptic error messages
- �?Lost data and incomplete operations
- �?Poor user experience
With robust error handling:
- �?Graceful degradation
- �?Automatic retry mechanisms
- �?Clear error messages
- �?Better user experience
- �?Easier debugging
Understanding SERP API Error Types
1. Network Errors
Common causes:
- Connection timeouts
- DNS resolution failures
- Network interruptions
- Firewall blocks
Example error:
{
"error": "ECONNREFUSED",
"message": "Connection refused",
"code": "NETWORK_ERROR"
}
2. HTTP Status Errors
Common status codes:
400 Bad Request- Invalid parameters401 Unauthorized- Invalid API key429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server-side issue503 Service Unavailable- Temporary outage
3. API Response Errors
SERPpost unified response format:
{
"code": -1001,
"msg": "Invalid search query",
"data": null
}
Common error codes:
0- Success-1001- Invalid parameters-1002- Authentication failed-1003- Insufficient credits-1004- Rate limit exceeded-1005- Service temporarily unavailable
4. Timeout Errors
Types:
- Connection timeout (establishing connection)
- Read timeout (waiting for response)
- Total request timeout
Basic Error Handling Patterns
JavaScript/Node.js
const axios = require('axios');
class SERPAPIClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://serppost.com/api';
}
async search(query, options = {}) {
try {
const response = await axios.get(`${this.baseURL}/search`, {
params: {
s: query,
t: options.engine || 'google',
p: options.page || 1
},
headers: {
'Authorization': `Bearer ${this.apiKey}`
},
timeout: options.timeout || 10000 // 10 seconds
});
// Check API response code
if (response.data.code !== 0) {
throw new APIError(response.data.msg, response.data.code);
}
return response.data.data;
} catch (error) {
return this.handleError(error);
}
}
handleError(error) {
// Network errors
if (error.code === 'ECONNREFUSED') {
throw new Error('Unable to connect to API server. Please check your network connection.');
}
if (error.code === 'ETIMEDOUT') {
throw new Error('Request timed out. Please try again.');
}
// HTTP errors
if (error.response) {
const status = error.response.status;
switch (status) {
case 400:
throw new Error('Invalid request parameters. Please check your query.');
case 401:
throw new Error('Invalid API key. Please check your credentials.');
case 429:
throw new Error('Rate limit exceeded. Please slow down your requests.');
case 500:
throw new Error('Server error. Please try again later.');
case 503:
throw new Error('Service temporarily unavailable. Please try again in a few minutes.');
default:
throw new Error(`HTTP ${status}: ${error.response.statusText}`);
}
}
// API-specific errors
if (error instanceof APIError) {
throw error;
}
// Unknown errors
throw new Error(`Unexpected error: ${error.message}`);
}
}
// Custom error class
class APIError extends Error {
constructor(message, code) {
super(message);
this.name = 'APIError';
this.code = code;
}
}
// Usage
const client = new SERPAPIClient('your_api_key');
try {
const results = await client.search('web scraping tools');
console.log('Search results:', results);
} catch (error) {
console.error('Search failed:', error.message);
// Handle error appropriately
}
Python
import requests
from typing import Optional, Dict, Any
import time
class APIError(Exception):
"""Custom exception for API errors"""
def __init__(self, message: str, code: int):
self.message = message
self.code = code
super().__init__(self.message)
class SERPAPIClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://serppost.com/api'
def search(
self,
query: str,
engine: str = 'google',
page: int = 1,
timeout: int = 10
) -> Dict[str, Any]:
"""
Search with comprehensive error handling
"""
try:
response = requests.get(
f'{self.base_url}/search',
params={
's': query,
't': engine,
'p': page
},
headers={
'Authorization': f'Bearer {self.api_key}'
},
timeout=timeout
)
# Raise HTTP errors
response.raise_for_status()
# Parse JSON response
data = response.json()
# Check API response code
if data['code'] != 0:
raise APIError(data['msg'], data['code'])
return data['data']
except requests.exceptions.Timeout:
raise TimeoutError(
f'Request timed out after {timeout} seconds. '
'Please try again or increase timeout.'
)
except requests.exceptions.ConnectionError:
raise ConnectionError(
'Unable to connect to API server. '
'Please check your network connection.'
)
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
if status_code == 400:
raise ValueError('Invalid request parameters')
elif status_code == 401:
raise PermissionError('Invalid API key')
elif status_code == 429:
raise RuntimeError('Rate limit exceeded')
elif status_code == 500:
raise RuntimeError('Server error. Please try again later')
elif status_code == 503:
raise RuntimeError('Service temporarily unavailable')
else:
raise RuntimeError(f'HTTP {status_code}: {e.response.text}')
except APIError:
raise
except Exception as e:
raise RuntimeError(f'Unexpected error: {str(e)}')
# Usage
client = SERPAPIClient('your_api_key')
try:
results = client.search('python web scraping')
print('Search results:', results)
except (TimeoutError, ConnectionError, ValueError, PermissionError, RuntimeError) as e:
print(f'Search failed: {e}')
# Handle error appropriately
Advanced Error Handling Strategies
1. Retry with Exponential Backoff
Why it matters:
- Temporary network issues often resolve quickly
- Exponential backoff prevents overwhelming the server
- Improves success rate by 80-90%
Implementation:
class RetryableAPIClient {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.baseURL = 'https://serppost.com/api';
this.maxRetries = options.maxRetries || 3;
this.initialDelay = options.initialDelay || 1000; // 1 second
}
async searchWithRetry(query, options = {}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await this.search(query, options);
} catch (error) {
lastError = error;
// Don't retry on certain errors
if (this.shouldNotRetry(error)) {
throw error;
}
// Last attempt failed
if (attempt === this.maxRetries) {
throw new Error(
`Failed after ${this.maxRetries + 1} attempts: ${error.message}`
);
}
// Calculate delay with exponential backoff
const delay = this.initialDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000; // Add randomness
console.log(
`Attempt ${attempt + 1} failed. Retrying in ${delay + jitter}ms...`
);
await this.sleep(delay + jitter);
}
}
throw lastError;
}
shouldNotRetry(error) {
// Don't retry on client errors
const nonRetryableErrors = [
'Invalid API key',
'Invalid request parameters',
'Insufficient credits'
];
return nonRetryableErrors.some(msg =>
error.message.includes(msg)
);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async search(query, options) {
// Implementation from previous example
// ...
}
}
// Usage
const client = new RetryableAPIClient('your_api_key', {
maxRetries: 3,
initialDelay: 1000
});
try {
const results = await client.searchWithRetry('seo tools');
console.log('Success:', results);
} catch (error) {
console.error('All retries failed:', error.message);
}
2. Circuit Breaker Pattern
Prevents cascading failures:
- Stops making requests after repeated failures
- Allows system to recover
- Automatically retries after cooldown period
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # Normal operation
OPEN = "open" # Blocking requests
HALF_OPEN = "half_open" # Testing recovery
class CircuitBreaker:
def __init__(
self,
failure_threshold: int = 5,
timeout: int = 60,
expected_exception: type = Exception
):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.expected_exception = expected_exception
self.failure_count = 0
self.last_failure_time = None
self.state = CircuitState.CLOSED
def call(self, func, *args, **kwargs):
"""Execute function with circuit breaker protection"""
if self.state == CircuitState.OPEN:
if self._should_attempt_reset():
self.state = CircuitState.HALF_OPEN
else:
raise RuntimeError(
f'Circuit breaker is OPEN. '
f'Try again after {self.timeout} seconds.'
)
try:
result = func(*args, **kwargs)
self._on_success()
return result
except self.expected_exception as e:
self._on_failure()
raise
def _should_attempt_reset(self) -> bool:
"""Check if enough time has passed to attempt reset"""
return (
self.last_failure_time and
datetime.now() - self.last_failure_time >=
timedelta(seconds=self.timeout)
)
def _on_success(self):
"""Reset circuit breaker on successful call"""
self.failure_count = 0
self.state = CircuitState.CLOSED
def _on_failure(self):
"""Handle failure"""
self.failure_count += 1
self.last_failure_time = datetime.now()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
print(
f'Circuit breaker opened after {self.failure_count} failures'
)
# Usage with SERP API
class ResilientSERPClient:
def __init__(self, api_key: str):
self.client = SERPAPIClient(api_key)
self.circuit_breaker = CircuitBreaker(
failure_threshold=5,
timeout=60,
expected_exception=RuntimeError
)
def search(self, query: str, **kwargs):
"""Search with circuit breaker protection"""
return self.circuit_breaker.call(
self.client.search,
query,
**kwargs
)
# Usage
client = ResilientSERPClient('your_api_key')
try:
results = client.search('market research tools')
print('Success:', results)
except RuntimeError as e:
print(f'Circuit breaker prevented request: {e}')
3. Fallback Mechanisms
Provide alternative responses when primary fails:
class FallbackAPIClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.cache = new Map();
}
async searchWithFallback(query, options = {}) {
try {
// Try primary search
const results = await this.search(query, options);
// Cache successful results
this.cache.set(query, {
data: results,
timestamp: Date.now()
});
return results;
} catch (error) {
console.warn('Primary search failed:', error.message);
// Fallback 1: Try cached results
const cached = this.getCachedResults(query);
if (cached) {
console.log('Using cached results');
return {
...cached,
fromCache: true,
warning: 'Using cached data due to API error'
};
}
// Fallback 2: Try alternative search engine
if (options.engine === 'google') {
console.log('Trying Bing as fallback...');
try {
return await this.search(query, { ...options, engine: 'bing' });
} catch (fallbackError) {
console.error('Fallback also failed:', fallbackError.message);
}
}
// Fallback 3: Return empty results with error message
return {
results: [],
error: error.message,
fallback: true
};
}
}
getCachedResults(query) {
const cached = this.cache.get(query);
if (!cached) return null;
// Check if cache is still valid (e.g., 1 hour)
const maxAge = 3600000; // 1 hour in milliseconds
const age = Date.now() - cached.timestamp;
if (age > maxAge) {
this.cache.delete(query);
return null;
}
return cached.data;
}
async search(query, options) {
// Implementation from previous examples
// ...
}
}
Timeout Management
Setting Appropriate Timeouts
const timeoutConfig = {
// Connection timeout: time to establish connection
connection: 5000, // 5 seconds
// Read timeout: time to receive response
read: 10000, // 10 seconds
// Total timeout: maximum time for entire request
total: 15000 // 15 seconds
};
async function searchWithTimeout(query, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
options.timeout || timeoutConfig.total
);
try {
const response = await fetch('https://serppost.com/api/search', {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
Rate Limit Handling
Detecting and Handling Rate Limits
import time
from datetime import datetime, timedelta
class RateLimitHandler:
def __init__(self, requests_per_second: int = 10):
self.requests_per_second = requests_per_second
self.min_interval = 1.0 / requests_per_second
self.last_request_time = None
self.retry_after = None
def wait_if_needed(self):
"""Wait if necessary to respect rate limits"""
# Check if we're in a rate limit cooldown
if self.retry_after and datetime.now() < self.retry_after:
wait_time = (self.retry_after - datetime.now()).total_seconds()
print(f'Rate limited. Waiting {wait_time:.1f} seconds...')
time.sleep(wait_time)
self.retry_after = None
# Enforce minimum interval between requests
if self.last_request_time:
elapsed = time.time() - self.last_request_time
if elapsed < self.min_interval:
time.sleep(self.min_interval - elapsed)
self.last_request_time = time.time()
def handle_rate_limit_error(self, retry_after_seconds: int = 60):
"""Handle 429 Too Many Requests error"""
self.retry_after = datetime.now() + timedelta(seconds=retry_after_seconds)
print(f'Rate limit hit. Will retry after {retry_after_seconds} seconds')
class RateLimitedClient:
def __init__(self, api_key: str, requests_per_second: int = 10):
self.client = SERPAPIClient(api_key)
self.rate_limiter = RateLimitHandler(requests_per_second)
def search(self, query: str, **kwargs):
"""Search with automatic rate limiting"""
# Wait if necessary
self.rate_limiter.wait_if_needed()
try:
return self.client.search(query, **kwargs)
except RuntimeError as e:
if 'Rate limit exceeded' in str(e):
self.rate_limiter.handle_rate_limit_error()
# Retry after waiting
self.rate_limiter.wait_if_needed()
return self.client.search(query, **kwargs)
raise
# Usage
client = RateLimitedClient('your_api_key', requests_per_second=5)
# Make multiple requests - automatically rate limited
queries = ['query1', 'query2', 'query3', 'query4', 'query5']
for query in queries:
try:
results = client.search(query)
print(f'Results for {query}:', len(results))
except Exception as e:
print(f'Error for {query}: {e}')
Logging and Monitoring
Comprehensive Error Logging
const winston = require('winston');
// Configure logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
class MonitoredAPIClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.metrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
totalLatency: 0
};
}
async search(query, options = {}) {
const startTime = Date.now();
this.metrics.totalRequests++;
try {
logger.info('API request started', {
query,
engine: options.engine || 'google',
timestamp: new Date().toISOString()
});
const results = await this.makeRequest(query, options);
const latency = Date.now() - startTime;
this.metrics.successfulRequests++;
this.metrics.totalLatency += latency;
logger.info('API request successful', {
query,
latency,
resultCount: results.length
});
return results;
} catch (error) {
const latency = Date.now() - startTime;
this.metrics.failedRequests++;
logger.error('API request failed', {
query,
error: error.message,
stack: error.stack,
latency,
timestamp: new Date().toISOString()
});
throw error;
}
}
getMetrics() {
const avgLatency = this.metrics.totalLatency / this.metrics.totalRequests;
const successRate = (this.metrics.successfulRequests / this.metrics.totalRequests) * 100;
return {
totalRequests: this.metrics.totalRequests,
successfulRequests: this.metrics.successfulRequests,
failedRequests: this.metrics.failedRequests,
successRate: successRate.toFixed(2) + '%',
averageLatency: avgLatency.toFixed(2) + 'ms'
};
}
async makeRequest(query, options) {
// Actual API request implementation
// ...
}
}
Best Practices Summary
1. Always Handle Errors
// �?Bad: No error handling
const results = await api.search('query');
// �?Good: Comprehensive error handling
try {
const results = await api.search('query');
// Process results
} catch (error) {
console.error('Search failed:', error.message);
// Handle error appropriately
}
2. Use Retry Logic
// �?Implement exponential backoff
const results = await apiClient.searchWithRetry('query', {
maxRetries: 3,
initialDelay: 1000
});
3. Set Appropriate Timeouts
// �?Set reasonable timeouts
const results = await api.search('query', {
timeout: 10000 // 10 seconds
});
4. Implement Fallbacks
// �?Provide fallback mechanisms
const results = await api.searchWithFallback('query');
// Returns cached data or alternative results if primary fails
5. Log Everything
// �?Log all errors for debugging
logger.error('API error', {
query,
error: error.message,
timestamp: new Date()
});
6. Monitor Metrics
// �?Track success rates and latency
const metrics = apiClient.getMetrics();
console.log('Success rate:', metrics.successRate);
console.log('Average latency:', metrics.averageLatency);
Production-Ready Error Handler
Here’s a complete, production-ready error handler combining all best practices:
class ProductionSERPClient {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.baseURL = 'https://serppost.com/api';
// Configuration
this.maxRetries = options.maxRetries || 3;
this.timeout = options.timeout || 10000;
this.rateLimit = options.rateLimit || 10; // requests per second
// State
this.cache = new Map();
this.circuitBreaker = new CircuitBreaker();
this.rateLimiter = new RateLimiter(this.rateLimit);
this.metrics = new MetricsCollector();
}
async search(query, options = {}) {
// Rate limiting
await this.rateLimiter.wait();
// Circuit breaker check
if (this.circuitBreaker.isOpen()) {
throw new Error('Circuit breaker is open. Service temporarily unavailable.');
}
// Try with retry logic
return await this.searchWithRetry(query, options);
}
async searchWithRetry(query, options, attempt = 0) {
try {
const results = await this.makeRequest(query, options);
// Success - update circuit breaker and cache
this.circuitBreaker.recordSuccess();
this.cacheResults(query, results);
this.metrics.recordSuccess();
return results;
} catch (error) {
this.circuitBreaker.recordFailure();
this.metrics.recordFailure(error);
// Check if should retry
if (attempt < this.maxRetries && this.shouldRetry(error)) {
const delay = this.calculateBackoff(attempt);
await this.sleep(delay);
return await this.searchWithRetry(query, options, attempt + 1);
}
// All retries failed - try fallback
return await this.handleFallback(query, options, error);
}
}
async handleFallback(query, options, originalError) {
// Try cache
const cached = this.getCached(query);
if (cached) {
return { ...cached, fromCache: true };
}
// Try alternative engine
if (options.engine === 'google') {
try {
return await this.makeRequest(query, { ...options, engine: 'bing' });
} catch (fallbackError) {
// Both failed
}
}
// No fallback available
throw originalError;
}
shouldRetry(error) {
// Don't retry client errors
const nonRetryable = ['401', '400', '403'];
return !nonRetryable.some(code => error.message.includes(code));
}
calculateBackoff(attempt) {
return Math.min(1000 * Math.pow(2, attempt), 10000);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... other helper methods
}
Conclusion
Robust error handling is essential for production SERP API integrations. By implementing:
- �?Comprehensive error detection
- �?Retry logic with exponential backoff
- �?Circuit breaker pattern
- �?Fallback mechanisms
- �?Proper timeout management
- �?Rate limit handling
- �?Logging and monitoring
You can build resilient applications that gracefully handle failures and provide excellent user experiences.
Ready to implement robust error handling?
Start with SERPpost and get 100 free credits to test your error handling strategies. Our API documentation provides detailed error codes and handling guidelines.
Related Articles
- SERP API Best Practices 2025
- SERP API Rate Limiting Best Practices
- SERP API Monitoring & Analytics
- Real-Time Search Results API
- SERP API for AI Agents & LLMs
About the Author: Alex Thompson is a DevOps Engineer at SERPpost with 10+ years of experience building resilient distributed systems. He specializes in API reliability, error handling patterns, and production monitoring. Alex has helped hundreds of companies improve their API integration reliability.
Need help with error handling? Check our documentation or try our API playground to test error scenarios safely.