Eine Airtable-Basis wird zur Warteschlange für CAPTCHA-Aufgaben, sobald ein einziges Feld die Arbeit steuert: Steht Status auf pending, startet eine Automatisierung, übergibt Sitekey und Page-URL an CaptchaAI und schreibt das gelöste Token in denselben Datensatz zurück. Weder ein eigener Server noch ein Zwischen-Tool ist nötig – zwei JavaScript-Skripte genügen, die Airtable selbst ausführt.
Der Aufbau folgt drei Schritten: Tabelle und Trigger, Skriptaktion für den einzelnen Datensatz, Scripting-Erweiterung für den Stapellauf.
Was Airtable-Skripte leisten – und wo die Grenzen liegen
Die Rahmenbedingungen der Plattform bestimmen, wie großzügig Sie das Polling auslegen dürfen:
- Automatisierungs-Skripte laufen serverseitig, also auch ohne geöffnete Basis – mit 30 Sekunden Laufzeit pro Ausführung.
- Die Scripting-Erweiterung im Apps-Bereich läuft im Browser-Tab: gut für Stapelläufe, verlangt aber die geöffnete Basis.
- Netzwerkaufrufe erfolgen über
fetch; in einzelnen Skriptkontexten heißt der AufrufremoteFetchAsync. - Der API-Schlüssel gehört als Eingabevariable über
input.config()in die Konfiguration, nicht fest in den Code.
Das 30-Sekunden-Limit ist entscheidend, denn reCAPTCHA v2 löst CaptchaAI typischerweise in unter 60 Sekunden. Kürzen Sie das Polling im Beispielskript auf rund 20 Sekunden Wartezeit und zwei bis drei Abfragen; längere Fälle gehören in den Stapellauf.
Praxisbeispiel: Lieferantenportale im DACH-Handel
Ein norddeutscher Großhändler pflegt in Airtable ein Register mit rund 40 Lieferantenportalen: Ansprechpartner, Abrufintervall, letzte Preisliste. Der wöchentliche Abruf läuft auf einem kleinen Hetzner-Server – doch vor dem Login mehrerer Portale steht ein reCAPTCHA v2.
Statt eines zweiten Werkzeugs übernimmt die vorhandene Basis diese Aufgabe: Der Wochenlauf setzt jeden fälligen Datensatz auf pending, die Automatisierung besorgt das Token, der Abrufjob liest es aus, sobald Status auf solved steht. Wichtig dabei: Die Zugänge sind vertraglich vereinbart, und da Kontaktdaten personenbezogen sein können, gehört der Datenfluss ins Verarbeitungsverzeichnis.
Tabelle anlegen: Feldnamen müssen zum Skript passen
Legen Sie eine Tabelle namens CAPTCHA Tasks an. Die Feldnamen bleiben englisch, weil updateRecordAsync sie wörtlich adressiert – auch die Auswahlwerte des Status-Feldes samt Schreibweise:
| Feld | Typ | Zweck |
|---|---|---|
URL |
URL | Adresse der Zielseite |
Sitekey |
Einzeiliger Text | reCAPTCHA-Sitekey der Seite |
Status |
Einfachauswahl | pending, solving, solved, failed |
Token |
Langer Text | gelöstes Token |
Solved At |
Datum/Uhrzeit | Zeitstempel der Lösung |
Error |
Einzeiliger Text | Fehlermeldung im Fehlerfall |
Deutsche Beschriftungen sind möglich, solange die Bezeichner im Skript unverändert bleiben.
Schritt 1: Automatisierung mit Status-Trigger anlegen
In Airtable:
- Öffnen Sie den Bereich Automatisierungen
- Klicken Sie auf Automatisierung erstellen
- Vergeben Sie einen sprechenden Namen, etwa „CAPTCHA bei neuem Datensatz lösen“
Trigger einrichten
Wählen Sie als Auslöser Wenn Datensatz Bedingungen erfüllt:
- Tabelle:
CAPTCHA Tasks - Bedingung:
Statusistpending
Der Status-Trigger ist bewusst allgemeiner als ein „neuer Datensatz“-Auslöser: Er greift auch beim manuellen Zurücksetzen eines gescheiterten Datensatzes. Ein erneuter Lauf kostet einen Klick. Drei Feinheiten sparen später Nacharbeit:
- Zweite Bedingung
Sitekeyist nicht leer – verhindert Leerläufe bei unvollständigen Zeilen - Eine Ansicht statt der ganzen Tabelle als Quelle, wenn nur ein Teilbestand automatisiert wird
- Beim Zurücksetzen auch
Errorleeren, damit keine alte Meldung neben einem frischen Token steht
Schritt 2: Skriptaktion mit dem CaptchaAI-Aufruf
Ergänzen Sie eine Aktion → Skript ausführen:
// 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}`);
}
Das Skript folgt dem zweistufigen API-Muster: in.php nimmt die Aufgabe mit method=userrecaptcha, googlekey und pageurl entgegen und liefert eine Task-ID, res.php gibt das Token zurück. Lautet die Antwort CAPCHA_NOT_READY, ist die Aufgabe noch in Arbeit – jede andere Rückmeldung beendet den Durchlauf als echter Fehler, statt einen falschen Sitekey minutenlang weiterzupollen.
Eingabevariablen zuordnen
Ordnen Sie im linken Bereich der Skriptaktion die drei Variablen zu:
recordId– Datensatz-ID aus dem Trigger-Schrittsitekey– FeldSitekeydes Datensatzespageurl– FeldURLdes Datensatzes
Legen Sie hier auch den API-Schlüssel als Eingabevariable an: Jede Person mit Zugriff auf die Basis kann den Skripttext lesen.
Schritt 3: Stapellauf über die Scripting-Erweiterung
Für Altbestände oder den wöchentlichen Sammellauf eignet sich die Scripting-Erweiterung aus dem Apps-Bereich. Sie arbeitet alle Datensätze mit dem Status pending nacheinander ab:
// 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');
Weil die Schleife sequenziell läuft, belegt der Stapellauf genau einen Thread. Mehr Parallelität erreichen Sie, indem Sie den Bestand auf mehrere Ansichten aufteilen und je Ansicht einen Lauf starten.
Threads planen: Was der Durchsatz kostet
CaptchaAI rechnet pro gleichzeitigem Thread ab – nicht pro Lösung. Ein Thread ist eine Aufgabe in Bearbeitung; ist sie fertig, nimmt derselbe Thread die nächste an. Tageslimits oder Aufschläge nach CAPTCHA-Typ gibt es nicht. Alle Preise in US-Dollar:
- BASIC (15 $/Monat, 5 Threads) – für einige Hundert Datensätze pro Tag, verteilt über den Tag
- STANDARD (30 $/Monat, 15 Threads) – wenn mehrere Sammelläufe parallel starten
- ADVANCE (90 $/Monat, 50 Threads) – für mehrere Basen und enge Zeitfenster
Bei einer Obergrenze von unter 60 Sekunden je reCAPTCHA v2 schafft ein Thread rechnerisch bis zu 60 Lösungen pro Stunde, fünf Threads also rund 300 – praktisch etwas weniger, weil Airtable zwischen Trigger und Skriptstart Zeit braucht. Entscheidend ist die Spitze, nicht die Tagesmenge.
Typische Fehlerbilder
| Symptom | Ursache | Lösung |
|---|---|---|
| Automatisierung startet nicht | Auswahlwert weicht ab (Schreibweise, Leerzeichen) | Statuswert exakt auf pending setzen, Trigger-Bedingung prüfen |
fetch is not defined |
In manchen Skriptkontexten trägt der Aufruf einen anderen Namen | remoteFetchAsync statt fetch verwenden |
| Skript bricht nach 30 Sekunden ab | Laufzeitlimit der Automatisierung erreicht | Wartezeit erhöhen, Iterationen reduzieren oder in den Stapellauf verlagern |
| Datensatz-Update schlägt fehl | Feldname im Skript weicht von der Tabelle ab | Bezeichner inklusive Solved At und Error abgleichen |
| Token wird erzeugt, aber von der Zielseite abgelehnt | Sitekey, Page-URL oder Sitzungskontext passen nicht | Parameter erneut erfassen, erst unmittelbar vor dem Absenden lösen |
Häufige Fragen
Wie lange bleibt ein gelöstes Token brauchbar?
Rund zwei Minuten. reCAPTCHA-v2-Tokens laufen nach etwa 120 Sekunden ab. Lösen Sie deshalb erst, wenn die Zielanfrage ansteht, und tragen Sie das Token direkt in das Formularfeld ein. Ein Token-Vorrat in der Airtable-Spalte ist schnell wertlos.
Läuft die Automatisierung auch ohne geöffnete Basis?
Ja. Automatisierungs-Skripte laufen auf Airtable-Servern. Die Scripting-Erweiterung dagegen führt Ihr Skript im Browser-Tab aus – wird die Basis geschlossen, bricht der Stapellauf ab.
Welche CAPTCHA-Typen deckt CaptchaAI in solchen Workflows ab?
reCAPTCHA v2 samt Invisible-, Callback- und Enterprise-Varianten, reCAPTCHA v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, Bild- und Rasterbild-CAPTCHAs sowie BLS; dazu CaptchaFox (Beta), Friendly Captcha (Beta) und Lemin (Beta). Nicht unterstützt sind hCaptcha und FunCaptcha; GeeTest v4 ist als „bald verfügbar“ angekündigt. Im Skript ändert sich dafür vor allem der method-Parameter.
Wie viele Threads brauche ich für 500 Datensätze pro Tag?
Bei verteilten Einträgen genügt BASIC (15 $/Monat, 5 Threads) mit Puffer. Eng wird es, wenn Importe große Mengen auf einmal auf pending setzen – dann entscheidet die gewünschte Bearbeitungsdauer über den Plan.
Dürfen die verarbeiteten Daten überhaupt in Airtable liegen?
Das entscheidet Ihre eigene Datenschutzprüfung. Ziel-URLs und Kontaktdaten können personenbezogen sein; klären Sie Rechtsgrundlage, Auftragsverarbeitung und Speicherort, bevor eine Basis produktiv geht. Tokens sind ohnehin kurzlebig – die Spalte Token sollte regelmäßig geleert werden.