Back to BlogTips
CAPTCHA API Error Handling: Production Best Practices
Build resilient integrations with proper timeout handling, retry logic, circuit breakers, and graceful degradation patterns.
reGOTCHA TeamDecember 12, 20256 min read
Why Error Handling Matters
CAPTCHA solving involves external APIs, network requests, and time-sensitive operations. Robust error handling prevents cascading failures and wasted resources.
Common Error Types
1. Network Errors
example.py
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def create_task(client, params):
try:
response = await client.post("/createTask", json=params)
response.raise_for_status()
return response.json()
except httpx.TimeoutException:
raise # Let tenacity retry
except httpx.HTTPStatusError as e:
if e.response.status_code >= 500:
raise # Retry server errors
raise e # Don't retry client errors2. API Errors
example.py
class CaptchaAPIError(Exception):
def __init__(self, error_id: int, description: str):
self.error_id = error_id
self.description = description
super().__init__(f"Error {error_id}: {description}")
ERROR_CODES = {
1: "Invalid API key",
2: "Insufficient balance",
3: "Task not found",
10: "Site key invalid",
12: "Task timeout",
}
def handle_api_response(data: dict):
if error_id := data.get("errorId"):
desc = ERROR_CODES.get(error_id, data.get("errorDescription", "Unknown"))
raise CaptchaAPIError(error_id, desc)3. Timeout Handling
example.py
import asyncio
async def solve_with_timeout(solver, params, timeout=120):
try:
return await asyncio.wait_for(
solver.solve(**params),
timeout=timeout
)
except asyncio.TimeoutError:
# Log for metrics
logger.warning(f"CAPTCHA solve timed out after {timeout}s")
raise
# With fallback
async def solve_with_fallback(solvers, params):
for solver in solvers:
try:
return await solve_with_timeout(solver, params)
except Exception as e:
logger.warning(f"Solver {solver.name} failed: {e}")
continue
raise Exception("All solvers failed")Circuit Breaker Pattern
example.py
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitBreaker:
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: int = 60,
half_open_max: int = 3
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_max = half_open_max
self.state = CircuitState.CLOSED
self.failures = 0
self.last_failure_time = None
self.half_open_calls = 0
async def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if self._should_try_reset():
self.state = CircuitState.HALF_OPEN
self.half_open_calls = 0
else:
raise Exception("Circuit breaker is open")
try:
result = await func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
def _should_try_reset(self):
return (
self.last_failure_time and
datetime.now() - self.last_failure_time >
timedelta(seconds=self.recovery_timeout)
)
def _on_success(self):
if self.state == CircuitState.HALF_OPEN:
self.half_open_calls += 1
if self.half_open_calls >= self.half_open_max:
self.state = CircuitState.CLOSED
self.failures = 0
def _on_failure(self):
self.failures += 1
self.last_failure_time = datetime.now()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPENGraceful Degradation
example.py
class CaptchaSolverWithFallback:
def __init__(self, primary_solver, fallback_strategy):
self.primary = primary_solver
self.fallback = fallback_strategy
self.circuit = CircuitBreaker()
async def solve(self, **params):
try:
return await self.circuit.call(
self.primary.solve,
**params
)
except Exception as e:
logger.warning(f"Primary solver failed: {e}")
return await self.fallback(**params)
# Fallback strategies
async def queue_for_manual_review(**params):
"""Queue the task for manual processing"""
await task_queue.push({
"type": "captcha_manual",
"params": params,
"created_at": datetime.now()
})
return None
async def skip_captcha_action(**params):
"""Skip the action requiring CAPTCHA"""
logger.info(f"Skipping CAPTCHA-protected action: {params}")
return NoneMonitoring & Alerting
example.py
from dataclasses import dataclass
from collections import deque
@dataclass
class SolverMetrics:
total_attempts: int = 0
successes: int = 0
failures: int = 0
timeouts: int = 0
avg_solve_time: float = 0.0
recent_errors: deque = None
def __post_init__(self):
self.recent_errors = deque(maxlen=100)
@property
def success_rate(self):
if self.total_attempts == 0:
return 0.0
return self.successes / self.total_attempts * 100
def should_alert(self):
# Alert if success rate drops below 80%
return self.success_rate < 80 and self.total_attempts > 10
# Usage
metrics = SolverMetrics()
async def solve_with_metrics(solver, params):
start = time.time()
metrics.total_attempts += 1
try:
result = await solver.solve(**params)
metrics.successes += 1
metrics.avg_solve_time = (
metrics.avg_solve_time * 0.9 +
(time.time() - start) * 0.1
)
return result
except asyncio.TimeoutError:
metrics.timeouts += 1
raise
except Exception as e:
metrics.failures += 1
metrics.recent_errors.append(str(e))
raisePro Tip: Implement health checks that verify your CAPTCHA solving pipeline is working before it's needed in production workflows.
APIError HandlingProductionBest Practices
Ready to solve CAPTCHAs at scale?
Get started with 50 free credits. No credit card required.