Solución de Problemas

Límites de concurrencia en CaptchaAI: diagnóstico y correcciones

ERROR_NO_SLOT_AVAILABLE no es una caída del servicio ni un problema con tu clave API: significa que, en ese instante, todos los threads incluidos en tu plan están ocupados resolviendo otras tareas. La corrección tiene dos mitades y las dos viven en tu código: pon un tope duro de concurrencia antes de llamar a la API y reintenta con retroceso exponencial en lugar de insistir a ciegas.

Debajo, en este orden:

  • cuántos threads incluye tu plan,
  • cómo distinguirlo del HTTP 429,
  • qué patrones sostienen el volumen alto.

Qué es un thread y por qué se agotan

CaptchaAI factura por thread concurrente, no por CAPTCHA resuelto. Un thread es un CAPTCHA en vuelo y queda libre en cuanto termina la resolución. Dentro de un plan las resoluciones son ilimitadas: lo que compras es la anchura del canal.

Plan Precio mensual Threads
BASIC $15 5
STANDARD $30 15
ADVANCE $90 50
PREMIUM $170 100
CORPORATE $240 150
ENTERPRISE $300 200
VIP-1 $1,500 1,000
VIP-2 $4,500 3,000
VIP-3 $7,500 5,000

Con esa lógica el error se explica solo: si lanzas 40 tareas a la vez sobre un plan STANDARD ($30/mes, 15 threads), las 15 primeras entran y las 25 restantes reciben ERROR_NO_SLOT_AVAILABLE. Consulta tu panel de control en captchaai.com para ver el límite vigente de tu cuenta antes de tocar nada más.

Los dos límites que casi todo el mundo confunde

Tipo de límite Qué controla Señal
Threads simultáneos Cuántas tareas se resuelven a la vez ERROR_NO_SLOT_AVAILABLE
Frecuencia de solicitudes Llamadas por segundo a in.php y res.php HTTP 429

Los arreglos son opuestos, así que conviene no confundirlos:

  • Threads agotados: baja el paralelismo o amplía los threads del plan.
  • HTTP 429: espacia las llamadas a res.php.

Cómo se manifiesta en tus logs

Lo que ves Causa probable
ERROR_NO_SLOT_AVAILABLE Demasiadas tareas activas a la vez
Respuestas HTTP 429 Demasiadas llamadas por segundo a la API
Los tiempos de resolución se alargan Congestión de cola en tu propia cuenta

Escenario: tres clientes compartiendo una clave API

Una agencia en Ciudad de México monitoriza precios en marketplaces del tipo MercadoLibre para tres clientes, con la misma clave de CaptchaAI en los tres cron. Los tres arrancan a las 9:00 y abren 20 hilos de trabajo cada uno: 60 tareas simultáneas contra un plan STANDARD de 15 threads. Los logs se llenan de ERROR_NO_SLOT_AVAILABLE durante los primeros minutos y luego todo se normaliza, lo que despista al equipo porque "a media mañana funciona".

Dos correcciones, en este orden:

  1. Desplaza los arranques (9:00, 9:05, 9:10) y asigna un presupuesto de concurrencia por cliente.
  2. Dimensiona el plan con datos: mide el tiempo real de resolución en tu entorno —los techos publicados son <60 s para reCAPTCHA v2 y <10 s para Cloudflare Turnstile— y divide tu volumen por hora entre lo que completa un thread. Si te salen 45 threads sostenidos, ADVANCE ($90/mes, 50 threads) encaja; forzar STANDARD solo traslada el problema a la cola de reintentos.

Paso 1: pon un semáforo delante de la API

El tope de concurrencia debe vivir en tu proceso, no depender de que la API te rechace:

import requests
import time
import threading

API_KEY = "YOUR_API_KEY"
MAX_CONCURRENT = 20  # Stay below your account limit

semaphore = threading.Semaphore(MAX_CONCURRENT)


def solve_captcha(params):
    """Solve a CAPTCHA with concurrency control."""
    with semaphore:
        params["key"] = API_KEY
        params["json"] = 1

        submit = requests.post("https://ocr.captchaai.com/in.php", data=params).json()
        if submit.get("status") != 1:
            raise RuntimeError(f"Submit: {submit.get('request')}")

        task_id = submit["request"]
        time.sleep(10)

        for _ in range(30):
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "get", "id": task_id, "json": 1
            }).json()
            if result.get("status") == 1:
                return result["request"]
            if result.get("request") != "CAPCHA_NOT_READY":
                raise RuntimeError(f"Solve: {result['request']}")
            time.sleep(5)
        raise TimeoutError("Timed out")

Deja MAX_CONCURRENT por debajo de los threads de tu plan: ese margen absorbe los reintentos y los procesos secundarios que comparten la clave.

Paso 2: reintenta con retroceso exponencial, no al instante

Un reintento inmediato llega cuando los slots siguen ocupados y solo añade tráfico. Espera cada vez un poco más y abandona tras un número finito de intentos:

def submit_with_retry(params, max_retries=5):
    """Submit with automatic retry for slot errors."""
    params["key"] = API_KEY
    params["json"] = 1

    for attempt in range(max_retries):
        resp = requests.post("https://ocr.captchaai.com/in.php", data=params).json()

        if resp.get("status") == 1:
            return resp["request"]

        error = resp.get("request", "")
        if error == "ERROR_NO_SLOT_AVAILABLE":
            wait = 2 ** attempt  # Exponential backoff: 1, 2, 4, 8, 16 seconds
            print(f"No slot available, retrying in {wait}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue
        else:
            raise RuntimeError(f"Submit error: {error}")

    raise RuntimeError("Max retries exceeded — no slots available")

Paso 3: cambia la ráfaga por una cola de tareas

Una cola con un número fijo de workers convierte la ráfaga en caudal constante. Úsala cuando:

  • llega un lote nocturno de miles de tareas,
  • un webhook dispara cientos de verificaciones de golpe.
from queue import Queue
from threading import Thread

task_queue = Queue()
results = {}


def worker():
    while True:
        task_id_local, params = task_queue.get()
        try:
            token = solve_captcha(params)
            results[task_id_local] = {"status": "ok", "token": token}
        except Exception as e:
            results[task_id_local] = {"status": "error", "message": str(e)}
        finally:
            task_queue.task_done()


# Start worker threads (limited by semaphore)
for _ in range(MAX_CONCURRENT):
    t = Thread(target=worker, daemon=True)
    t.start()

# Add tasks to queue
captcha_tasks = [
    {"method": "userrecaptcha", "googlekey": "KEY1", "pageurl": "https://site1.com"},
    {"method": "userrecaptcha", "googlekey": "KEY2", "pageurl": "https://site2.com"},
    # ... more tasks
]

for i, params in enumerate(captcha_tasks):
    task_queue.put((i, params))

task_queue.join()
print(f"Completed: {len(results)} tasks")

Paso 4: ajusta el ritmo del sondeo

Consultar el resultado cada segundo no acelera nada y te acerca al HTTP 429. Espera de forma generosa la primera vez y después sondea con calma:

# WRONG — polling every 1 second
time.sleep(1)

# CORRECT — poll every 5 seconds
time.sleep(5)

# BETTER — wait longer on initial delay, then poll
time.sleep(15)  # Initial wait
for _ in range(20):
    # ... poll
    time.sleep(5)

Instrumenta cuántos threads tienes ocupados

Antes de subir de plan, comprueba si de verdad saturas el que ya tienes. Un contador con lock te dice si el pico es real o solo un arranque mal repartido:

active_count = 0
lock = threading.Lock()

def track_solve(params):
    global active_count
    with lock:
        active_count += 1
        print(f"Active tasks: {active_count}/{MAX_CONCURRENT}")
    try:
        return solve_captcha(params)
    finally:
        with lock:
            active_count -= 1

Iguala el entorno entre local y CI

Buena parte de los fallos "de concurrencia" en pruebas automatizadas son en realidad diferencias de entorno. Mantén viewport, idioma y user-agent idénticos en todos los runners:

from selenium import webdriver

def make_driver(headless: bool = True) -> webdriver.Chrome:
    options = webdriver.ChromeOptions()
    if headless:
        options.add_argument('--headless=new')
    options.add_argument('--window-size=1280,800')
    options.add_argument('--lang=es-ES')
    return webdriver.Chrome(options=options)

Buenas prácticas antes de cambiar de plan

  • Vigila los threads ocupados en el pico: es el dato que decide si necesitas más capacidad o solo repartir mejor los arranques.
  • Agrupa los errores por código: un repunte de ERROR_NO_SLOT_AVAILABLE es un problema de capacidad, no de calidad.
  • Usa una clave API separada para QA, distinta de la de producción, para no mezclar métricas ni threads.
  • Versiona tus snapshots de configuración (sitekey, action, umbrales) con los tests.
  • Prueba siempre sobre tu propia aplicación o entornos autorizados, respetando los términos de servicio y la normativa de protección de datos aplicable.

Preguntas frecuentes

¿Cuántos threads necesito para mi volumen?

Depende del tipo de CAPTCHA. Calcula cuántas tareas completa un thread por hora en tu entorno y divide tu volumen horario entre esa cifra. Los techos publicados (<10 s en Cloudflare Turnstile, <60 s en reCAPTCHA v2) sirven como peor caso.

¿Los threads se reparten entre tipos de CAPTCHA?

Sí. Tu plan da un total de threads simultáneos, no una cuota por tipo. Un lote de reCAPTCHA v2 y otro de GeeTest v3 lanzados a la vez compiten por los mismos slots, así que planifica sobre el pico agregado.

¿Puedo usar varias claves API para saltarme el límite?

No es el camino: el límite es de cuenta y multiplicar claves te deja con métricas fragmentadas y facturación duplicada. Si el pico es sostenido, sube de plan; si es puntual, aplana la carga con una cola.

¿Cuánto conviene esperar antes de descartar una tarea?

Cinco intentos con retroceso exponencial (1, 2, 4, 8 y 16 segundos) cubren la mayoría de los picos cortos. Si al terminar ese ciclo sigues sin slot, registra el fallo, devuelve la tarea a la cola y revisa tus threads en lugar de alargar los reintentos.


Escala tu resolución con CaptchaAI

Revisa tus threads y ajusta tu plan en captchaai.com.


Guías relacionadas

Valida tus integraciones de CAPTCHA en entornos propios con CaptchaAI.

Los comentarios están deshabilitados para este artículo.