Antes de la lista completa, quédate con una idea: la API de CaptchaAI casi nunca falla "al azar". Cada respuesta con status: 0 dice una de seis cosas — tus credenciales no sirven, tus parámetros o tu archivo no cuadran, tu proxy no llega, el servidor tropezó, ese desafío no salió, o todavía no está listo. La clase, y no el código exacto, decide si reintentas, si corriges y reenvías, o si solo esperas.
Los errores llegan por dos endpoints distintos:
in.php— envías la tarea; aquí saltan los problemas de clave, de parámetros y de archivo.res.php— consultas el resultado; aquí saltan los problemas del propio desafío y del proxy durante la resolución.
Si añades json=1 a la solicitud, el error viaja como JSON:
{"status": 0, "request": "ERROR_CODE_HERE"}
Sin json=1 recibes el mismo código en texto plano: ERROR_CODE_HERE. Usa siempre json=1: parsear texto plano es la primera fuente de bugs.
Los códigos de error de CaptchaAI en tres reglas
| Patrón de error | Qué hacer |
|---|---|
CAPCHA_NOT_READY |
Es normal: vuelve a consultar el resultado a los 5 segundos |
Cualquier ERROR_ de parámetro o formato |
Corrige la solicitud; no reenvíes la misma tarea sin cambios |
Errores de servidor (ERROR_SERVER_ERROR, ERROR_INTERNAL_SERVER_ERROR) |
Reintenta a los 10 segundos con retroceso exponencial |
A partir de aquí los códigos van agrupados por esas clases, que es como se depuran en la práctica.
Errores de credenciales y de cuenta
Descártalos primero: si falla la clave o la capacidad de threads, ningún ajuste de parámetros arregla nada.
ERROR_WRONG_USER_KEY
Causa: el parámetro key tiene un formato incorrecto. Las claves API de CaptchaAI tienen exactamente 32 caracteres.
Solución:
- Cuenta los caracteres de tu clave: deben ser 32.
- Revisa que no arrastre espacios ni saltos de línea.
- Cópiala de nuevo desde tu panel de API.
Incorrecto:
{
"key": "abc123... "
}
Correcto:
{
"key": "abc12345678901234567890123456789a"
}
ERROR_KEY_DOES_NOT_EXIST
Causa: la clave tiene el formato correcto, pero no corresponde a ninguna cuenta del sistema.
Solución:
- Inicia sesión en captchaai.com y copia la clave desde tu panel de control.
- Comprueba que no estés mezclando la clave de una cuenta de pruebas con la de producción.
- Si acabas de crear la cuenta, espera unos minutos hasta que la clave quede activa.
ERROR_ZERO_BALANCE
Causa: tu cuenta no tiene threads libres para aceptar la tarea.
Solución:
- Espera a que terminen las tareas en curso; cada una libera su thread al completarse.
- Sube de plan si tu volumen ya no cabe en la capacidad actual.
- Revisa el estado de tu cuenta en captchaai.com/api.php.
El nombre engaña. CaptchaAI factura por thread concurrente, no por resolución, y cada plan incluye resoluciones ilimitadas. Este código suele significar "todos tus threads están ocupados ahora mismo". Con BASIC ($15/mes, 5 threads) y cinco tareas en vuelo, la sexta devuelve
ERROR_ZERO_BALANCEhasta que una termine; STANDARD ($30/mes, 15 threads) y ADVANCE ($90/mes, 50 threads) amplían ese margen.
IP_BANNED
Causa: tu IP quedó bloqueada temporalmente tras varios intentos de autenticación fallidos seguidos.
Solución: detén el proceso, espera unos 5 minutos y vuelve con las credenciales correctas. Un worker que reintenta en bucle con una clave inválida renueva el bloqueo en cada intento: corta al primer error de clave.
Errores de parámetros del desafío
La clave es válida y hay threads libres, pero la descripción del desafío no cuadra con la página real.
ERROR_PAGEURL
Causa: el parámetro pageurl falta o llega vacío. Es obligatorio en todos los CAPTCHA basados en token (reCAPTCHA, Cloudflare Turnstile, GeeTest v3, etc.).
Solución: envía la URL completa de la página donde se carga el CAPTCHA, protocolo incluido.
Incorrecto:
{
"pageurl": ""
}
Correcto:
{
"pageurl": "https://staging.example.com/qa-login"
}
ERROR_WRONG_GOOGLEKEY / ERROR_GOOGLEKEY
Causa: el parámetro googlekey (el sitekey) llega vacío, truncado o con un formato inválido.
Solución:
- Vuelve a extraer el sitekey del atributo
data-sitekeyde la página, o del parámetrokde la URL de anclaje de reCAPTCHA. - Verifica que el valor no venga cortado: un regex mal anclado suele recortar los últimos caracteres.
Incorrecto:
{
"googlekey": ""
}
Correcto:
{
"googlekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
}
ERROR_BAD_TOKEN_OR_PAGEURL
Causa: el par googlekey + pageurl no es válido en conjunto. El sitekey existe, pero no está registrado para esa URL.
Causas frecuentes:
- El widget vive en un iframe de otro subdominio y tú envías la URL de la página contenedora.
- El sitekey pertenece a otra página o a otro dominio del mismo sitio.
- Copiaste el sitekey de staging y lo usas contra producción, o al revés.
Solución:
- Si el widget vive en un iframe, usa la URL de su atributo
srccomopageurl. - Toma el sitekey de la misma página que vas a resolver, no de la portada.
- Comprueba el par cargando
https://www.google.com/recaptcha/api2/anchor?k=YOUR_SITEKEY: si no renderiza el widget, el par está mal.
Un caso típico en portales hispanohablantes. Los portales de cita previa y de trámites públicos — desde los centros de visados BLS hasta sedes electrónicas como la del SAT mexicano — suelen montar el formulario protegido en un iframe con dominio propio, distinto del que ves en la barra del navegador. Si envías la URL exterior, recibes ERROR_BAD_TOKEN_OR_PAGEURL una y otra vez aunque el sitekey sea correcto. Inspecciona el DOM, localiza el iframe real y envía su src. Y respeta siempre los términos de servicio del portal y la normativa de protección de datos aplicable.
ERROR_BAD_PARAMETERS
Causa: faltan parámetros obligatorios del método, o llegan con el tipo de dato equivocado.
Solución: contrasta tu payload con la lista mínima de cada tipo antes de reenviar.
| Tipo de CAPTCHA | Parámetros obligatorios |
|---|---|
| reCAPTCHA v2/v3 | key, method=userrecaptcha, googlekey, pageurl |
| Cloudflare Turnstile | key, method=turnstile, sitekey, pageurl |
| Cloudflare Challenge | key, method=cloudflare_challenge, pageurl, proxy, proxytype |
| GeeTest v3 | key, method=geetest, gt, challenge, pageurl |
| BLS | key, method=bls, body, textinstructions |
| Normal/imagen | key, method=post, file o body |
Un detalle que provoca muchos diagnósticos equivocados: reCAPTCHA usa googlekey y Turnstile usa sitekey. Cruzar ambos nombres devuelve ERROR_BAD_PARAMETERS aunque el valor sea correcto.
ERROR_WRONG_ID_FORMAT
Causa: el ID de la tarea debe ser únicamente numérico y estás enviando otra cosa.
Solución: envía exactamente el ID que devolvió in.php, sin comillas, sin el prefijo OK| y sin espacios. Si parseas texto plano, este error es la señal para pasarte a json=1.
ERROR_WRONG_CAPTCHA_ID
Causa: el ID no existe o ya caducó.
Solución:
- Confirma que consultas el resultado con el ID de tu propio envío, no con uno guardado de otra ejecución.
- Los ID caducan pasado cierto tiempo: si la tarea es muy antigua, envíala de nuevo en lugar de insistir.
Errores de imagen y archivo
Solo aparecen en los métodos que suben contenido (method=post, BLS y similares). Cuatro de ellos se diagnostican de un vistazo:
| Código | Qué pasó | Qué hacer |
|---|---|---|
ERROR_TOO_BIG_CAPTCHA_FILESIZE |
La imagen supera el tamaño máximo permitido. | Comprime o redimensiona antes de enviar; recortar el área del desafío suele bastar. JPEG para fotos, PNG para capturas. |
ERROR_ZERO_CAPTCHA_FILESIZE |
El archivo pesa menos de 100 bytes: subida vacía o dañada. | Comprueba que envías datos de imagen reales. Suele venir de capturar el elemento antes de que termine de cargarse. |
ERROR_WRONG_FILE_EXTENSION |
La extensión no está admitida; se aceptan jpg, jpeg, png y gif. |
Convierte la imagen antes de subirla. Renombrar un .webp a .png no sirve: el servidor mira el contenido, no el nombre. |
ERROR_IMAGE_TYPE_NOT_SUPPORTED |
El servidor no logra deducir el tipo de imagen a partir del contenido. | Conviértela a PNG o JPEG estándar y comprueba que el archivo no esté dañado. |
ERROR_UPLOAD
Causa: el servidor no pudo leer el archivo subido ni el payload base64.
Solución:
- En subidas de archivo, revisa la codificación de tu formulario multipart.
- En base64, comprueba que la cadena esté completa y bien codificada.
- Prueba con una imagen que sepas que funciona.
Errores de proxy
Aparecen al enviar y al consultar el resultado, y comparten diagnóstico: el proxy es tuyo, así que la comprobación empieza fuera de CaptchaAI.
ERROR_BAD_PROXY
Causa: el proxy que indicaste es inaccesible o el sistema lo marcó como defectuoso.
Solución:
- Pruébalo por tu cuenta: ¿llega ese proxy al sitio de destino desde tu máquina?
- Prueba con otro proxy antes de tocar nada más en tu código.
- Revisa el formato:
login:password@IP:PORT, oIP:PORTen proxies autenticados por IP.
El uso de proxy debe estar habilitado en tu cuenta. Si nunca lo activaste, escribe al soporte de CaptchaAI antes de seguir depurando.
ERROR_PROXY_CONNECTION_FAILED
Causa: durante la resolución, el solver no consiguió conectarse al sitio de destino a través de tu proxy.
Solución:
- El proxy puede estar caído de forma pasajera; cambia a otro y reintenta.
- El sitio de destino puede estar rechazando esa IP de salida.
- Verifica que el proxy alcance ese dominio concreto, no solo internet en general.
Errores de servidor
ERROR_SERVER_ERROR / ERROR_INTERNAL_SERVER_ERROR
Causa: un fallo pasajero del lado del servidor.
Solución: espera 10 segundos y reintenta con retroceso exponencial. Es la única familia de códigos donde reintentar sin cambiar nada es la respuesta correcta:
import time
retry_delay = 10
for attempt in range(5):
response = submit_captcha()
if response.get("status") == 1:
break
time.sleep(retry_delay)
retry_delay *= 2 # 10s, 20s, 40s, 80s, 160s
Sondeo del resultado (res.php)
CAPCHA_NOT_READY
No es un error. Significa que la resolución sigue en curso.
Acción: espera 5 segundos y vuelve a consultar el resultado.
if result.get("request") == "CAPCHA_NOT_READY":
time.sleep(5)
continue # poll again
Guía de tiempos:
| Tipo de CAPTCHA | Primera consulta a los | Intervalo entre consultas |
|---|---|---|
| reCAPTCHA v2/v3/Enterprise | 15 segundos | 5 segundos |
| Cloudflare Turnstile | 15 segundos | 5 segundos |
| Cloudflare Challenge | 20 segundos | 5 segundos |
| GeeTest v3 | 15 segundos | 5 segundos |
| CAPTCHA normal/imagen | 5 segundos | 5 segundos |
Consultar antes de tiempo no acelera nada: solo multiplica solicitudes.
ERROR_CAPTCHA_UNSOLVABLE
Causa: CaptchaAI no consiguió resolver ese desafío concreto después de varios intentos.
Motivos habituales:
- El tipo de CAPTCHA no es compatible, o los parámetros no describen bien el desafío.
- El desafío llegó caducado o dañado.
- En resoluciones con proxy, el proxy es demasiado lento o inestable.
- El sitio cambió su implementación del CAPTCHA.
Solución:
- Revisa sitekey,
pageurlymethodantes que nada. - Envía una tarea nueva, no el mismo ID.
- Si usas proxy, prueba con otro.
- Si el error se vuelve constante en un sitio que antes funcionaba, vuelve a extraer sitekey y URL: probablemente movieron el widget.
Un
ERROR_CAPTCHA_UNSOLVABLEaislado entre cientos de resoluciones es ruido normal. Vigila la tasa: si se sostiene por encima del 1–2%, el problema está en tus parámetros o en el sitio.
ERROR_EMPTY_ACTION
Causa: falta el parámetro action en la solicitud de sondeo, o llega vacío.
Solución: añade action=get a tu llamada a res.php:
params = {
"key": api_key,
"action": "get", # Required
"id": captcha_id,
"json": 1,
}
ERROR_WRONG_USER_KEY / ERROR_KEY_DOES_NOT_EXIST en res.php
También aparecen al consultar el resultado, con la misma causa y solución que en el envío. Si salen aquí y no en in.php, suele ser porque el worker que sondea lee la clave de otra variable de entorno.
Plantilla de manejo de errores
El patrón es el mismo en cualquier lenguaje: dos conjuntos de códigos — los que hay que corregir y los que se reintentan —, un backoff y un bucle de sondeo con límite.
Python
import time
import requests
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
# Errors that should not be retried (fix the request first)
NO_RETRY_ERRORS = {
"ERROR_WRONG_USER_KEY",
"ERROR_KEY_DOES_NOT_EXIST",
"ERROR_PAGEURL",
"ERROR_WRONG_GOOGLEKEY",
"ERROR_GOOGLEKEY",
"ERROR_BAD_TOKEN_OR_PAGEURL",
"ERROR_BAD_PARAMETERS",
"ERROR_WRONG_FILE_EXTENSION",
"ERROR_IMAGE_TYPE_NOT_SUPPORTED",
"IP_BANNED",
}
# Errors that can be retried
RETRY_ERRORS = {
"ERROR_ZERO_BALANCE",
"ERROR_SERVER_ERROR",
"ERROR_INTERNAL_SERVER_ERROR",
"ERROR_UPLOAD",
}
def solve_captcha(submit_data, max_retries=3, max_polls=60):
"""Submit and solve a CAPTCHA with full error handling."""
# Submit with retry logic
for attempt in range(max_retries):
resp = requests.post(SUBMIT_URL, data={**submit_data, "json": 1}, timeout=30)
resp.raise_for_status()
data = resp.json()
if data.get("status") == 1:
captcha_id = data["request"]
break
error = data.get("request", "UNKNOWN")
if error in NO_RETRY_ERRORS:
raise ValueError(f"Fatal error (fix request): {error}")
if error in RETRY_ERRORS and attempt < max_retries - 1:
time.sleep(10 * (2 ** attempt))
continue
raise RuntimeError(f"Submit failed: {error}")
else:
raise RuntimeError("Submit failed after max retries")
# Poll for result
time.sleep(15)
for _ in range(max_polls):
resp = requests.get(
RESULT_URL,
params={"key": API_KEY, "action": "get", "id": captcha_id, "json": 1},
timeout=30,
)
data = resp.json()
if data.get("request") == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if data.get("status") == 1:
return data["request"]
error = data.get("request", "UNKNOWN")
if error == "ERROR_CAPTCHA_UNSOLVABLE":
raise RuntimeError("CAPTCHA unsolvable — resubmit with fresh parameters")
raise RuntimeError(f"Poll error: {error}")
raise TimeoutError("Solve timed out")
Node.js
const NO_RETRY_ERRORS = new Set([
"ERROR_WRONG_USER_KEY",
"ERROR_KEY_DOES_NOT_EXIST",
"ERROR_PAGEURL",
"ERROR_WRONG_GOOGLEKEY",
"ERROR_BAD_TOKEN_OR_PAGEURL",
"ERROR_BAD_PARAMETERS",
"IP_BANNED",
]);
async function solveCaptcha(submitData, maxRetries = 3, maxPolls = 60) {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Submit with retry
let captchaId;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ ...submitData, json: "1" }),
});
const data = await resp.json();
if (data.status === 1) {
captchaId = data.request;
break;
}
if (NO_RETRY_ERRORS.has(data.request)) {
throw new Error(`Fatal error: ${data.request}`);
}
if (attempt < maxRetries - 1) {
await sleep(10_000 * 2 ** attempt);
continue;
}
throw new Error(`Submit failed: ${data.request}`);
}
// Poll for result
await sleep(15_000);
for (let i = 0; i < maxPolls; i++) {
const resp = await fetch(
`https://ocr.captchaai.com/res.php?${new URLSearchParams({
key: submitData.key,
action: "get",
id: captchaId,
json: "1",
})}`
);
const data = await resp.json();
if (data.request === "CAPCHA_NOT_READY") {
await sleep(5_000);
continue;
}
if (data.status === 1) return data.request;
throw new Error(`Poll error: ${data.request}`);
}
throw new Error("Solve timed out");
}
Preguntas frecuentes
¿ERROR_ZERO_BALANCE significa que me quedé sin saldo?
Casi nunca. CaptchaAI cobra por threads concurrentes con resoluciones ilimitadas, así que lo normal es que todos tus threads estén ocupados en ese instante. Pon una cola con límite de concurrencia igual a tus threads; si aun así se repite en horas punta, tu volumen pide el siguiente plan.
¿Cuánto debo esperar antes de la primera consulta a res.php?
Entre 5 y 20 segundos según el tipo: 5 segundos para CAPTCHA de imagen, 15 para reCAPTCHA, Turnstile y GeeTest v3, y 20 para Cloudflare Challenge. Después, un intervalo fijo de 5 segundos.
¿Puedo reintentar el mismo ID después de ERROR_CAPTCHA_UNSOLVABLE?
No. Ese ID ya está cerrado y devolverá siempre lo mismo. Envía una tarea nueva con parámetros recién extraídos; si se repite en el mismo sitio, revisa el sitekey y la pageurl.
¿Qué hago si recibo IP_BANNED en producción?
Detén los reintentos. El bloqueo dura unos 5 minutos, pero se renueva con cada intento fallido, así que un worker en bucle puede quedarse bloqueado indefinidamente. Corrige la clave y arranca de nuevo con un solo proceso de prueba.
¿Cambian los códigos si no uso json=1?
No: el código es el mismo, cambia el envoltorio. Con json=1 recibes {"status": 0, "request": "..."} y en texto plano solo la cadena. Trabaja con JSON y te ahorras el parseo frágil que produce falsos ERROR_WRONG_ID_FORMAT.
Guías relacionadas
- Inicio rápido con la API de CaptchaAI — tu primera resolución funcionando
- Resolver reCAPTCHA v2 con la API — el tutorial completo, paso a paso
- Cloudflare Challenge y su proxy obligatorio — el flujo donde más aparecen los errores de proxy
- Errores frecuentes al resolver reCAPTCHA v2 — diagnóstico específico de reCAPTCHA