Si la API te devuelve ERROR_ZERO_BALANCE, no hay nada roto en tu código: tu cuenta de CaptchaAI se quedó sin fondos y el servidor rechaza cada tarea antes de intentar resolverla. La corrección inmediata es recargar en captchaai.com; la duradera es que tu automatización lo detecte, avise a tiempo y aguante el golpe sin tirar el proceso. Aquí van ambas, en orden.
Confirma el saldo antes de tocar el código
Antes de revisar reintentos o cabeceras, haz la comprobación barata:
- Consulta el saldo con
action=getbalanceenres.php. - Si devuelve
0.0000, ya tienes el diagnóstico y el resto de la depuración sobra.
import requests
def check_balance(api_key):
"""Check current CaptchaAI balance."""
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": api_key, "action": "getbalance", "json": 1},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
return float(data["request"])
raise RuntimeError(f"Balance check failed: {data.get('request')}")
balance = check_balance("YOUR_API_KEY")
print(f"Balance: ${balance:.4f}")
Guárdala en tu módulo de utilidades y llámala al arrancar el proceso: ese chequeo de dos segundos evita horas de investigación sobre un pipeline "que dejó de funcionar sin motivo".
Qué provoca realmente el saldo cero
Cuatro causas explican casi todos los casos:
| Causa | Frecuencia | Qué hacer |
|---|---|---|
| Saldo de la cuenta agotado | La más común | Añade fondos en captchaai.com |
| Consumo mayor al previsto | Frecuente | Activa un monitor de saldo |
| Clave API expuesta por accidente | Poco frecuente | Rota la clave API y revisa el uso |
| Método de pago caducado | Ocasional | Actualiza los datos de facturación |
La tercera fila merece atención especial. Si tu clave terminó en un repositorio público o en el frontend de una aplicación, el saldo cae a una velocidad que no coincide con tu volumen real. Cuando el consumo se dispara sin que crezca tu tráfico, trátalo como una fuga de credenciales y no como un problema de presupuesto: rota la clave primero y revisa los registros después.
Maneja el saldo cero sin tumbar el proceso
Un scraper que revienta con una excepción no controlada a las tres de la madrugada cuesta mucho más que los dólares que faltaban. El patrón es sencillo: comprueba el saldo de forma periódica, cachea el resultado unos minutos y lanza una excepción propia y descriptiva cuando se cruza el umbral, en vez de dejar que el error llegue crudo desde in.php.
import requests
import time
import logging
logger = logging.getLogger(__name__)
class BalanceAwareSolver:
"""Solver that handles zero balance without crashing."""
def __init__(self, api_key, min_balance=0.50):
self.api_key = api_key
self.min_balance = min_balance
self._last_balance_check = 0
self._cached_balance = None
def solve(self, params):
"""Solve CAPTCHA with balance pre-check."""
# Check balance every 5 minutes
if time.time() - self._last_balance_check > 300:
self._check_balance()
if self._cached_balance is not None and self._cached_balance < 0.01:
raise InsufficientBalanceError(
f"Balance too low: ${self._cached_balance:.4f}. "
"Add funds at https://captchaai.com"
)
try:
return self._submit_and_poll(params)
except ZeroBalanceError:
self._cached_balance = 0.0
logger.error("ERROR_ZERO_BALANCE — add funds at captchaai.com")
raise
def _check_balance(self):
"""Check and cache balance."""
try:
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={
"key": self.api_key,
"action": "getbalance",
"json": 1,
},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
self._cached_balance = float(data["request"])
self._last_balance_check = time.time()
if self._cached_balance < self.min_balance:
logger.warning(
f"Low balance: ${self._cached_balance:.4f} "
f"(threshold: ${self.min_balance:.2f})"
)
except Exception as e:
logger.debug(f"Balance check failed: {e}")
def _submit_and_poll(self, params):
"""Submit task and poll for result."""
data = {"key": self.api_key, "json": 1, **params}
resp = requests.post(
"https://ocr.captchaai.com/in.php", data=data, timeout=30,
)
result = resp.json()
if result.get("status") != 1:
error = result.get("request", "")
if error == "ERROR_ZERO_BALANCE":
raise ZeroBalanceError("Account balance is zero")
raise RuntimeError(f"Submit failed: {error}")
task_id = result["request"]
time.sleep(10)
for _ in range(24):
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
},
timeout=15,
)
data = resp.json()
if data.get("status") == 1:
return data["request"]
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
class ZeroBalanceError(Exception):
"""Raised when account has no balance."""
pass
class InsufficientBalanceError(Exception):
"""Raised when balance is below minimum threshold."""
pass
Dos detalles importan en producción:
- El sondeo espera 10 segundos antes del primer intento y luego consulta cada 5 segundos, un ritmo razonable para la API.
- La consulta de saldo va en su propio
try/except: un fallo de red al comprobarlo no debe impedir que se resuelva un CAPTCHA que sí podía resolverse.
Alertas de saldo bajo: enterarte antes que tu cliente
Enterarte del saldo cero por el mensaje de un cliente es la peor opción. Un hilo en segundo plano que consulte el saldo cada diez minutos y avise por correo o Slack lo evita con poco código.
import smtplib
from email.message import EmailMessage
import threading
import time
import logging
logger = logging.getLogger(__name__)
class BalanceMonitor:
"""Monitor balance and send alerts when low."""
def __init__(self, api_key, alert_threshold=1.00, check_interval=600):
self.api_key = api_key
self.alert_threshold = alert_threshold
self.check_interval = check_interval
self._alert_sent = False
self._running = False
def start(self):
"""Start background monitoring."""
self._running = True
thread = threading.Thread(target=self._monitor_loop, daemon=True)
thread.start()
logger.info("Balance monitor started")
def stop(self):
"""Stop monitoring."""
self._running = False
def _monitor_loop(self):
"""Check balance periodically."""
while self._running:
try:
balance = self._get_balance()
logger.info(f"Balance: ${balance:.4f}")
if balance <= 0:
self._send_alert("CRITICAL: CaptchaAI Zero Balance",
f"Balance is ${balance:.4f}. Solving will fail.")
elif balance < self.alert_threshold and not self._alert_sent:
self._send_alert("WARNING: CaptchaAI Low Balance",
f"Balance: ${balance:.4f} (threshold: ${self.alert_threshold:.2f})")
self._alert_sent = True
elif balance >= self.alert_threshold:
self._alert_sent = False # Reset alert flag
except Exception as e:
logger.error(f"Monitor error: {e}")
time.sleep(self.check_interval)
def _get_balance(self):
"""Check account balance."""
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": self.api_key, "action": "getbalance", "json": 1},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
return float(data["request"])
raise RuntimeError(data.get("request"))
def _send_alert(self, subject, body):
"""Send email alert. Replace with your notification method."""
logger.critical(f"{subject}: {body}")
# Implement email, Slack webhook, or other notification here
# Usage
monitor = BalanceMonitor("YOUR_API_KEY", alert_threshold=2.00)
monitor.start()
Un ejemplo concreto: una agencia en Ciudad de México que monitoriza precios en marketplaces regionales concentra su ventana de captura entre las 22:00 y las 02:00. Si el saldo se agota a las 23:10 y el aviso llega al día siguiente, esa noche de datos se pierde. Con un umbral de advertencia en $2.00 y una alerta crítica al llegar a cero, quien esté de guardia recarga en minutos. Ajusta el umbral a tu consumo por hora, no a una cifra redonda: si no te da tiempo de reaccionar, es decoración.
Estima el consumo antes de lanzar un lote grande
Conviene distinguir dos modelos de gasto:
- Por thread. Los planes de CaptchaAI se facturan por thread, no por resolución: van desde BASIC ($15/mes, 5 threads) hasta VIP-3 ($7,500/mes, 5.000 threads), con resoluciones ilimitadas por thread durante el mes. Tu límite es la concurrencia contratada, no un contador que se vacía.
- Contra saldo. El gasto sigue al volumen real, así que la aritmética previa evita el corte a mitad de ejecución.
Este bloque estima el gasto del lote y te dice si el saldo lo cubre:
# Approximate costs per CAPTCHA type
COST_PER_SOLVE = {
"recaptcha_v2": 0.003,
"recaptcha_v3": 0.004,
"turnstile": 0.002,
"geetest": 0.003,
"image": 0.001,
"bls": 0.002,
}
def estimate_cost(captcha_type, quantity):
"""Estimate cost for a batch of solves."""
rate = COST_PER_SOLVE.get(captcha_type, 0.003)
total = rate * quantity
return total
def check_budget(api_key, captcha_type, planned_solves):
"""Check if balance covers planned solves."""
balance = check_balance(api_key)
estimated = estimate_cost(captcha_type, planned_solves)
if balance >= estimated:
print(f"Budget OK: ${balance:.4f} covers ~{int(balance / COST_PER_SOLVE[captcha_type])} solves")
return True
else:
shortfall = estimated - balance
print(f"Need ${shortfall:.4f} more for {planned_solves} {captcha_type} solves")
return False
# Check before a large batch
check_budget("YOUR_API_KEY", "recaptcha_v2", 5000)
Los precios se mantienen siempre en USD, como en la página de precios de CaptchaAI. Para agencias y freelancers que facturan en pesos o soles, ese costo mensual fijo en dólares es lo que hace previsible el presupuesto aunque el tipo de cambio se mueva.
Degradación controlada: saltar, encolar o parar
Cuando el saldo llega a cero a mitad de una ejecución tienes tres estrategias, y la correcta depende del trabajo:
- Saltar (
skip): en scraping de catálogos amplios, donde perder algunos elementos no invalida el conjunto. - Encolar (
queue): para tareas que importan una por una — formularios, citas, verificaciones — porque se reintentan al volver el saldo. - Parar (
raise): en procesos transaccionales, donde continuar a medias deja datos inconsistentes.
class GracefulSolver:
"""Fall back to manual or skip when balance is zero."""
def __init__(self, api_key, on_zero_balance="skip"):
self.api_key = api_key
self.on_zero_balance = on_zero_balance # "skip", "queue", "raise"
self._pending_queue = []
self.solver = BalanceAwareSolver(api_key)
def solve_or_degrade(self, params, item_id=None):
"""Try to solve, degrade gracefully on zero balance."""
try:
return self.solver.solve(params)
except (ZeroBalanceError, InsufficientBalanceError):
return self._handle_zero(params, item_id)
def _handle_zero(self, params, item_id):
"""Handle zero balance based on configured strategy."""
if self.on_zero_balance == "skip":
logger.warning(f"Skipping CAPTCHA for item {item_id} — no balance")
return None
elif self.on_zero_balance == "queue":
self._pending_queue.append({"params": params, "item_id": item_id})
logger.info(f"Queued item {item_id} — {len(self._pending_queue)} pending")
return None
else: # "raise"
raise ZeroBalanceError("No balance — stopping automation")
def retry_pending(self):
"""Retry queued items after balance is refilled."""
if not self._pending_queue:
return []
results = []
remaining = []
for item in self._pending_queue:
try:
token = self.solver.solve(item["params"])
results.append({"item_id": item["item_id"], "token": token})
except (ZeroBalanceError, InsufficientBalanceError):
remaining.append(item)
break # Stop retrying — still no balance
self._pending_queue = remaining + self._pending_queue[len(results) + len(remaining):]
return results
Deja la estrategia explícita en la configuración, no implícita en el código: dentro de seis meses lo agradecerás cuando toque explicar por qué faltan 300 filas.
Tabla de diagnóstico rápido
| Síntoma | Causa probable | Solución |
|---|---|---|
ERROR_ZERO_BALANCE en todas las solicitudes |
Cuenta sin fondos | Añade fondos en captchaai.com |
| El saldo baja mucho más rápido de lo previsto | Clave API expuesta o código que reenvía tareas | Rota la clave y revisa los registros de uso |
| El saldo aparece positivo pero el error persiste | Desfase de caché o de sincronización | Espera 1 minuto y reintenta |
| No consigues añadir fondos | Método de pago rechazado o caducado | Actualiza el método de pago en el panel de control |
| El error solo aparece en algunos workers | Claves distintas por proceso | Verifica qué clave usa cada worker |
Preguntas frecuentes
¿ERROR_ZERO_BALANCE significa que mi clave API es inválida?
No. Una clave incorrecta devuelve un error de clave, no de saldo: ERROR_ZERO_BALANCE confirma que la clave se reconoce y que solo faltan fondos.
¿Por qué sigo viendo el error justo después de recargar?
Suele ser un desfase de sincronización de segundos. Espera un minuto, consulta de nuevo con getbalance y reintenta. Si tu integración cachea el saldo, invalida la caché antes de dar por bueno el resultado.
¿Qué saldo mínimo conviene mantener?
Calcula tu consumo en una hora punta y multiplícalo por lo que tardas en recargar fuera del horario laboral. Ese es tu umbral realista; por debajo, el aviso llega tarde.
¿Puede el error indicar que mi clave está expuesta?
Sí, si viene con un consumo que no cuadra con tu tráfico. Rota la clave API desde el panel de control, revisa el historial de uso y guarda la clave en una variable de entorno o un gestor de secretos.
¿Qué plan elijo si me quedo sin saldo cada mes?
Si el corte se repite, el problema es de modelo y no de recarga. Un plan por threads con resoluciones ilimitadas convierte el gasto variable en una cuota fija en USD: empieza por BASIC ($15/mes, 5 threads) y sube cuando tu límite sea la concurrencia.
Guías relacionadas
Mantén el saldo por encima de tu umbral de alerta: recarga tu cuenta en CaptchaAI y deja el monitor corriendo.