Sí, se puede resolver un CAPTCHA sin salir de Airtable: una automatización detecta el registro, un script corto envía el sitekey a CaptchaAI y el token vuelve escrito en el mismo registro que lo pidió. Sin servidor intermedio ni cron externo. Abajo montamos el circuito con reCAPTCHA v2: tabla, disparador, script, lotes y los fallos del primer día. Antes de empezar necesitas tres cosas:
- Permisos para crear automatizaciones en la base.
- Tu API key de CaptchaAI, visible en el panel de control.
- El sitekey de la página protegida y su URL exacta.
Cada CAPTCHA en vuelo ocupa un thread: BASIC ($15/mes, 5 threads) cubre una base pequeña y ADVANCE ($90/mes, 50 threads) aguanta lotes largos.
Un caso concreto: QA de formularios en una agencia
Una agencia en Ciudad de México guarda en Airtable las landing pages de sus clientes y revisa cada semana que el formulario de contacto siga recibiendo leads. Todos llevan reCAPTCHA v2, así que en vez de abrir página por página basta con marcar el registro como "pending":
- La automatización detecta el registro pendiente.
- El script envía el sitekey y la URL a CaptchaAI.
- CaptchaAI devuelve el token resuelto.
- El token se guarda en el registro con su marca de tiempo.
Trabaja siempre sobre sitios propios o autorizados y respeta la normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México).
Los campos que debe tener tu tabla
| Campo | Tipo en Airtable | Para qué sirve |
|---|---|---|
| URL | URL | Dirección de la página de destino |
| Sitekey | Texto de una línea | Sitekey de reCAPTCHA |
| Status | Selección única | pending, solving, solved, failed |
| Token | Texto largo | Token devuelto por CaptchaAI |
| Solved At | Fecha/hora | Momento en que se resolvió |
| Error | Texto de una línea | Mensaje de error si algo falla |
- La tabla se llama CAPTCHA Tasks y el script la busca con ese nombre exacto.
- Los campos van en inglés: cualquier variación rompe
updateRecordAsync. - Añade un campo numérico de reintentos.
Paso 1: crear la automatización
- Abre la pestaña Automations de tu base.
- Pulsa Create automation.
- Nómbrala "Resolver CAPTCHA en registro pendiente".
El disparador correcto
- Tipo: When record matches conditions, nunca "when record created".
- Tabla: CAPTCHA Tasks.
- Condición: Status es "pending".
Así el flujo se dispara con registros nuevos y cada vez que devuelves uno a "pending": es como reintentas un fallo sin duplicar filas.
Paso 2: añadir la acción de script
Añade una acción -> Run a script y pega este código:
// Airtable Automation Script — Solve CAPTCHA via CaptchaAI
// Input configuration (set in the left panel):
// - recordId: Record ID from trigger
// - sitekey: Sitekey field from trigger
// - pageurl: URL field from trigger
const config = input.config();
const recordId = config.recordId;
const sitekey = config.sitekey;
const pageurl = config.pageurl;
const API_KEY = 'YOUR_API_KEY'; // Use input.config() for security
// Update status to "solving"
const table = base.getTable('CAPTCHA Tasks');
await table.updateRecordAsync(recordId, {
'Status': { name: 'solving' },
});
try {
// Step 1: Submit task to CaptchaAI
const submitUrl = `https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`;
const submitResponse = await fetch(submitUrl);
const submitResult = await submitResponse.json();
if (submitResult.status !== 1) {
throw new Error(`Submit failed: ${submitResult.request}`);
}
const taskId = submitResult.request;
console.log(`Task submitted: ${taskId}`);
// Step 2: Poll for result (wait 15 seconds first)
await new Promise(resolve => setTimeout(resolve, 15000));
let token = null;
for (let i = 0; i < 20; i++) {
const pollUrl = `https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const pollResponse = await fetch(pollUrl);
const pollResult = await pollResponse.json();
if (pollResult.status === 1) {
token = pollResult.request;
break;
}
if (pollResult.request !== 'CAPCHA_NOT_READY') {
throw new Error(`Solve failed: ${pollResult.request}`);
}
await new Promise(resolve => setTimeout(resolve, 5000));
}
if (!token) {
throw new Error('Polling timeout — CAPTCHA not solved in time');
}
// Step 3: Update record with solved token
await table.updateRecordAsync(recordId, {
'Status': { name: 'solved' },
'Token': token,
'Solved At': new Date().toISOString(),
'Error': '',
});
console.log(`CAPTCHA solved for record ${recordId}`);
} catch (error) {
// Update record with error
await table.updateRecordAsync(recordId, {
'Status': { name: 'failed' },
'Error': error.message,
});
console.error(`Failed: ${error.message}`);
}
Fíjate en el orden que sigue el script:
- Marca el registro como "solving".
- Envía la tarea a
in.php. - Espera 15 segundos antes del primer sondeo.
- Consulta
res.phpcada 5 segundos hasta que llegue el token.
Sondear antes solo gasta iteraciones.
Conectar las variables de entrada
| Variable | Qué le asignas |
|---|---|
recordId |
El ID del registro que activó la automatización |
sitekey |
El campo Sitekey de ese registro |
pageurl |
El campo URL de ese registro |
Mapea las tres en el panel izquierdo. Si alguna falta, el registro se queda en "solving" para siempre.
Paso 3: resolver por lotes con la extensión de scripting
La automatización procesa un registro a la vez. Con cincuenta URL de golpe conviene la extensión de scripting, que recorre la cola entera:
// Batch CAPTCHA Solver — Airtable Scripting Extension
const API_KEY = 'YOUR_API_KEY';
const table = base.getTable('CAPTCHA Tasks');
// Get all pending records
const query = await table.selectRecordsAsync({
fields: ['URL', 'Sitekey', 'Status'],
});
const pendingRecords = query.records.filter(
r => r.getCellValueAsString('Status') === 'pending'
);
output.text(`Found ${pendingRecords.length} pending CAPTCHAs`);
for (const record of pendingRecords) {
const sitekey = record.getCellValueAsString('Sitekey');
const pageurl = record.getCellValueAsString('URL');
if (!sitekey || !pageurl) {
output.text(`Skipping ${record.id} — missing sitekey or URL`);
continue;
}
output.text(`Solving for: ${pageurl}`);
await table.updateRecordAsync(record.id, {
'Status': { name: 'solving' },
});
try {
// Submit
const submitResp = await fetch(
`https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`
);
const submitData = await submitResp.json();
if (submitData.status !== 1) throw new Error(submitData.request);
// Poll
await new Promise(r => setTimeout(r, 15000));
let token = null;
for (let i = 0; i < 20; i++) {
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${submitData.request}&json=1`
);
const pollData = await pollResp.json();
if (pollData.status === 1) { token = pollData.request; break; }
if (pollData.request !== 'CAPCHA_NOT_READY') throw new Error(pollData.request);
await new Promise(r => setTimeout(r, 5000));
}
if (!token) throw new Error('Timeout');
await table.updateRecordAsync(record.id, {
'Status': { name: 'solved' },
'Token': token,
'Solved At': new Date().toISOString(),
});
output.text(`✓ Solved: ${pageurl}`);
} catch (e) {
await table.updateRecordAsync(record.id, {
'Status': { name: 'failed' },
'Error': e.message,
});
output.text(`✗ Failed: ${e.message}`);
}
}
output.text('Batch processing complete');
Qué se rompe la primera vez (y cómo arreglarlo)
| Síntoma | Causa habitual | Cómo se resuelve |
|---|---|---|
| La automatización nunca arranca | El valor de Status no coincide con la condición | La opción debe llamarse exactamente "pending" |
fetch is not defined |
Airtable expone remoteFetchAsync en ese contexto |
Sustituye fetch por remoteFetchAsync |
| El script se corta a medias | Límite de 30 segundos por automatización | Espera inicial de 20 segundos y menos iteraciones |
updateRecordAsync devuelve error |
Un nombre de campo no coincide | Cópialos tal como aparecen en la tabla |
| La API key queda a la vista | Está escrita dentro del script | Pásala con input.config() como variable de entrada |
Preguntas frecuentes
¿Necesito saber programar para montar esto?
Poco. El disparador se configura con clics y el script se pega tal cual.
¿Cuántos CAPTCHA puedo resolver a la vez desde una base?
Tantos como threads tenga tu plan: 5 con BASIC ($15/mes), 15 con STANDARD ($30/mes), 50 con ADVANCE ($90/mes). El límite es la concurrencia, no el volumen mensual.
¿Sirve para Cloudflare Turnstile o solo para reCAPTCHA v2?
También para Turnstile, reCAPTCHA v3, GeeTest v3 y CAPTCHA de imagen: solo cambia el parámetro method. hCaptcha y FunCaptcha no están soportados, y GeeTest v4 figura como próximamente.
¿Cómo evito que mi API key quede visible para todo el equipo?
En automatizaciones, declárala como variable de entrada del script. En la extensión de scripting, pídela en tiempo de ejecución con input.textAsync().
¿Qué hago con los registros que fallan?
Vuelve a ponerlos en "pending" y la automatización los recoge otra vez. Con el contador de reintentos puedes cortar al tercer intento y revisar el sitekey.
Sigue por aquí
- reCAPTCHA v2 con callback: cómo entregar el token
- Cuando conviven reCAPTCHA v2 y Turnstile en la misma página
- Cómo funciona el callback de reCAPTCHA v2
Otras automatizaciones sin código: