Cambiar de CapMonster Cloud a CaptchaAI es un trabajo de tarde: tocas la URL base, el parámetro de la clave y la forma en que lees la respuesta. Abajo tienes el mapa de equivalencias, el punto donde casi todos se tropiezan y una checklist de corte.
El motivo rara vez es técnico: es de presupuesto. CapMonster Cloud cobra por resolución, así que la factura sube justo cuando el proyecto empieza a funcionar. CaptchaAI cobra por thread concurrente, con resoluciones ilimitadas dentro del plan. Para una agencia que factura en moneda local y paga en USD, pasar de coste variable a cuota fija (BASIC, $15/mes, 5 threads) es lo que permite presupuestar.
Los tres cambios que importan de verdad
Casi toda la migración cabe en esta tabla.
| Componente | CapMonster Cloud | CaptchaAI |
|---|---|---|
| URL de envío | https://api.capmonster.cloud/createTask |
https://ocr.captchaai.com/in.php |
| URL de resultado | https://api.capmonster.cloud/getTaskResult |
https://ocr.captchaai.com/res.php |
| Parámetro de la clave | clientKey |
key |
| Formato | cuerpo JSON | codificado en formulario (o JSON) |
| ID de tarea | taskId |
request |
| Resultado | objeto solution |
request (token) |
Lo esencial: cambia el endpoint, el parámetro de la clave y dónde vive el token. Lo demás se deriva de ahí.
La ruta rápida: dos constantes y a probar
Si tus llamadas pasan por un wrapper o un SDK, empieza por aquí: cambia las constantes y ejecuta tu suite.
# Before (CapMonster Cloud)
API_URL = "https://api.capmonster.cloud"
CLIENT_KEY = "your_capmonster_key"
# After (CaptchaAI)
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
API_KEY = "your_captchaai_key"
Si arranca y tu wrapper ya normalizaba la respuesta, has terminado. Si no, la diferencia está en el parseo.
Migración completa de reCAPTCHA v2
Compara los dos bloques línea por línea: la lógica de sondeo es la misma, cambia el vocabulario.
Cómo lo hacías con CapMonster Cloud
import requests
import time
resp = requests.post("https://api.capmonster.cloud/createTask", json={
"clientKey": "CAPMONSTER_KEY",
"task": {
"type": "RecaptchaV2TaskProxyless",
"websiteURL": "https://example.com",
"websiteKey": "6Le-SITEKEY",
}
}).json()
task_id = resp["taskId"]
while True:
time.sleep(5)
result = requests.post("https://api.capmonster.cloud/getTaskResult", json={
"clientKey": "CAPMONSTER_KEY",
"taskId": task_id,
}).json()
if result["status"] == "ready":
token = result["solution"]["gRecaptchaResponse"]
break
Cómo queda con CaptchaAI
import requests
import time
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "6Le-SITEKEY",
"pageurl": "https://example.com",
"json": "1",
}).json()
task_id = resp["request"]
while True:
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": "1",
}).json()
if result["status"] == 1:
token = result["request"]
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(result["request"])
Fíjate en el final: CaptchaAI devuelve CAPCHA_NOT_READY mientras la tarea sigue en cola, y cualquier otro valor en request es un error que debes propagar. Esa comprobación evita bucles infinitos con la clave mal puesta o el saldo a cero.
Migración en Node.js
En JavaScript pasas de un cuerpo JSON a parámetros de query.
Cómo lo hacías con CapMonster Cloud
const axios = require('axios');
const resp = await axios.post('https://api.capmonster.cloud/createTask', {
clientKey: 'CAPMONSTER_KEY',
task: {
type: 'RecaptchaV2TaskProxyless',
websiteURL: 'https://example.com',
websiteKey: '6Le-SITEKEY',
}
});
const taskId = resp.data.taskId;
Cómo queda con CaptchaAI
const axios = require('axios');
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'userrecaptcha',
googlekey: '6Le-SITEKEY',
pageurl: 'https://example.com',
json: 1,
}
});
const taskId = resp.data.request;
El único ajuste conceptual es que resp.data.taskId pasa a ser resp.data.request. Si tipas la respuesta en TypeScript, ahí te avisará el compilador.
Consultar el saldo
Detalle fácil de olvidar: tus alertas de saldo bajo apuntan a otro endpoint. En CaptchaAI la consulta reutiliza res.php con action=getbalance.
CapMonster Cloud
resp = requests.post("https://api.capmonster.cloud/getBalance", json={
"clientKey": "CAPMONSTER_KEY"
}).json()
balance = resp["balance"]
CaptchaAI
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "getbalance",
"json": "1",
}).json()
balance = float(resp["request"])
Envuélvelo en float(): la API devuelve el saldo como cadena en request.
Equivalencias de parámetros por tipo de CAPTCHA
Comprobación del primer día: CaptchaAI resuelve reCAPTCHA v2 y v3 (invisible, callback y Enterprise), Cloudflare Turnstile y Challenge, GeeTest v3, imagen/OCR, grid y BLS, más CaptchaFox, Friendly Captcha y Lemin en beta. hCaptcha y FunCaptcha (Arkose Labs) no son compatibles; GeeTest v4 figura como próximamente.
reCAPTCHA v2
| CapMonster Cloud | CaptchaAI |
|---|---|
task.type: "RecaptchaV2TaskProxyless" |
method: "userrecaptcha" |
task.websiteKey |
googlekey |
task.websiteURL |
pageurl |
task.isInvisible: true |
invisible: "1" |
Cloudflare Turnstile
| CapMonster Cloud | CaptchaAI |
|---|---|
task.type: "TurnstileTaskProxyless" |
method: "turnstile" |
task.websiteKey |
sitekey |
task.websiteURL |
pageurl |
CAPTCHA de imagen
| CapMonster Cloud | CaptchaAI |
|---|---|
task.type: "ImageToTextTask" |
method: "base64" |
task.body |
body |
Para GeeTest v3 o grid el patrón es idéntico: cambia method y los parámetros del widget. La estructura de envío y sondeo no varía entre tipos.
Códigos de error: donde falla la mitad de las migraciones
| CapMonster Cloud | Equivalente en CaptchaAI |
|---|---|
ERROR_KEY_DOES_NOT_EXIST |
ERROR_KEY_DOES_NOT_EXIST |
ERROR_ZERO_BALANCE |
ERROR_ZERO_BALANCE |
ERROR_RECAPTCHA_TIMEOUT |
ERROR_CAPTCHA_UNSOLVABLE |
ERROR_NO_SLOT_AVAILABLE |
ERROR_NO_SLOT_AVAILABLE |
CAPTCHA_NOT_READY |
CAPCHA_NOT_READY |
Mira la última fila: CaptchaAI escribe CAPCHA_NOT_READY, sin la T. No es una errata de este artículo, es el valor literal de la API. Si mantienes la comparación contra CAPTCHA_NOT_READY, tu código leerá "todavía no está lista" como error fatal y abortará cada tarea a los cinco segundos.
ERROR_NO_SLOT_AVAILABLE tampoco es una caída: has agotado los threads del plan. Si es constante, sube de tier (BASIC $15/mes con 5 threads, STANDARD $30/mes con 15, ADVANCE $90/mes con 50).
Checklist de corte
Los ocho pasos, en este orden, antes de mover tráfico real:
- [] Obtén tu clave API en captchaai.com
- [] URL de envío:
createTaskporocr.captchaai.com/in.php - [] URL de resultado:
getTaskResultporocr.captchaai.com/res.php - [] Autenticación:
clientKeyporkey - [] Formato: del objeto JSON
taska parámetros codificados en formulario - [] Parseo:
taskIdporrequestysolution.gRecaptchaResponseporrequest - [] Errores:
CAPTCHA_NOT_READYporCAPCHA_NOT_READY - [] Prueba una sola resolución antes de tocar producción
El último punto no es decorativo: una resolución real contra staging valida clave, endpoint, parseo y verificación de backend de una pasada.
Un corte progresivo, con números
Caso frecuente en agencias de la región: un portal de trámites propio con reCAPTCHA v2 en el alta y una suite nocturna de QA que hace unas 400 resoluciones contra staging. Con precio por resolución esa cifra es una línea variable; con planes por thread caben de sobra en BASIC ($15/mes, 5 threads), porque el límite es cuántas tareas tienes en vuelo a la vez, no cuántas resuelves al mes.
Corta por porcentaje: una semana con el 10% de las tareas de QA en CaptchaAI y, si cuadran los números, sube al 50% y luego al 100%. Usa la misma configuración de navegador en QA, staging y CI.
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)
Con viewport, idioma y user-agent idénticos desaparece el clásico "en local pasa, en CI falla". Durante el corte mide en tu endpoint: tiempo de resolución, tasa de éxito de la verificación de backend, errores por código y latencia extremo a extremo. Usa una clave API separada para QA y respeta los términos de servicio y la normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México).
Solución de problemas
| Síntoma | Acción recomendada |
|---|---|
| Cada tarea aborta a los 5 segundos | Compara contra CAPCHA_NOT_READY, sin la T |
ERROR_KEY_DOES_NOT_EXIST con la clave correcta |
Envías clientKey en lugar de key |
| El token llega vacío | Lee request, no solution.gRecaptchaResponse |
ERROR_NO_SLOT_AVAILABLE recurrente |
Threads del plan agotados: sube de tier |
| El test pasa en local y falla en CI | Iguala viewport, idioma y user-agent |
Preguntas frecuentes
¿Cuánto tiempo real tarda la migración?
Entre 15 y 30 minutos si tus llamadas están encapsuladas en un módulo. Si están repartidas, lo que tarda es encontrarlas: haz un grep de capmonster sobre el repositorio.
¿Puedo mantener los dos servicios a la vez?
Sí, y es la forma recomendada de cortar. Nada impide tener ambos clientes activos mientras validas resultados.
¿Cambia el coste si mi volumen sube de golpe?
No dentro del mismo plan. Al facturar por thread concurrente con resoluciones ilimitadas, un pico se traduce en tareas encoladas, no en una factura sorpresa.
¿Qué pasa con los tipos que CaptchaAI no resuelve?
hCaptcha y FunCaptcha (Arkose Labs) no son compatibles y GeeTest v4 figura como próximamente. Si tu flujo depende de alguno, una migración parcial es más realista.
Empieza con una sola resolución de prueba
Crea tu cuenta en captchaai.com y lanza una resolución contra tu formulario de staging. Si el token pasa la verificación del backend, ya has migrado.