Setiap respons dari API CaptchaAI hanya jatuh ke salah satu dari tiga bentuk: baris yang diawali OK| (berhasil), string CAPCHA_NOT_READY (task masih diproses), atau kode yang diawali ERROR_ (ada yang gagal). Begitu Anda mengenali ketiga pola ini, seluruh alur in.php dan res.php bisa ditangani dengan satu blok logika yang sama — tanpa perlu parser JSON yang berat, karena format CaptchaAI memang berbasis teks sederhana.
Referensi ini merinci setiap format yang akan Anda temui beserta cara mengurainya di Python dan Node.js. Penanganan respons yang rapi jadi pembeda antara pipeline yang stabil dan yang macet di tengah jalan, terutama saat kode Anda berjalan di produksi — misalnya sebuah scraper yang dideploy di region AWS ap-southeast-3 (Jakarta) dengan jaringan yang kadang fluktuatif.
Tiga bentuk respons yang wajib Anda kenali
Alih-alih menghafal puluhan kemungkinan output, cukup cek respons terhadap tiga pola berurutan. Yang berubah antar tipe CAPTCHA hanyalah isi payload setelah OK| — token tunggal untuk reCAPTCHA dan Turnstile, beberapa field untuk GeeTest v3, atau teks hasil OCR untuk CAPTCHA gambar. Kerangka pengecekannya tetap sama.
| Bentuk | Contoh | Artinya | Tindakan |
|---|---|---|---|
Diawali OK| |
OK|73548291 |
Berhasil — payload ada setelah tanda pipa | Ambil bagian setelah OK| |
CAPCHA_NOT_READY |
CAPCHA_NOT_READY |
Task belum selesai | Tunggu 5 detik, polling lagi |
Diawali ERROR_ |
ERROR_ZERO_BALANCE |
Gagal — kode menjelaskan sebabnya | Cek kode di tabel error |
Endpoint kirim (in.php)
Endpoint in.php menerima task dan langsung mengembalikan task ID — bukan hasil solve-nya. Balasan sukses berupa OK|TASK_ID; kalau baris pertama sudah berupa kode ERROR_, jangan lanjut polling dan perbaiki dulu request-nya.
OK|TASK_ID
ERROR_CODE
Simpan angka setelah OK| (contoh OK|73548291) sebagai task ID — nilai inilah yang Anda pakai untuk polling. Satu percabangan startsWith("OK|") sudah cukup memisahkan sukses dari gagal, dan pola yang sama Anda pakai lagi di res.php:
resp = requests.get("https://ocr.captchaai.com/in.php", params={...})
if resp.text.startswith("OK|"):
task_id = resp.text.split("|")[1]
else:
error = resp.text
raise Exception(f"Submit failed: {error}")
const resp = await axios.get("https://ocr.captchaai.com/in.php", { params });
if (resp.data.startsWith("OK|")) {
const taskId = resp.data.split("|")[1];
} else {
throw new Error(`Submit failed: ${resp.data}`);
}
Endpoint polling (res.php)
Setelah punya task ID, lakukan polling ke res.php sampai hasilnya siap. Selama task masih diproses, balasannya CAPCHA_NOT_READY — ini balasan paling sering di beberapa percobaan pertama, jadi jadikan jalur normal, bukan kondisi error. Tunggu 5 detik lalu polling lagi.
CAPCHA_NOT_READY
Untuk reCAPTCHA v2/v3 dan Cloudflare Turnstile, payload sukses berupa satu token panjang. Token inilah yang Anda kirim balik ke situs target: sebagai g-recaptcha-response untuk reCAPTCHA, atau cf-turnstile-response untuk Turnstile.
OK|03AGdBq24PBCbw...long_token_string
Untuk CAPTCHA gambar/OCR, teks setelah OK| adalah teks yang dikenali dari gambar — cukup kirim string tersebut sebagai jawaban.
OK|abc123
GeeTest v3 mengembalikan beberapa field sekaligus, dipisah koma. Pisahkan tiap field sebelum diteruskan ke API target:
OK|challenge:abc123,validate:def456,seccode:ghi789
if result.text.startswith("OK|"):
data = result.text.split("|")[1]
parts = dict(item.split(":") for item in data.split(","))
challenge = parts["challenge"]
validate = parts["validate"]
seccode = parts["seccode"]
Cloudflare Challenge mengembalikan nilai cookie qa_validation_cookie beserta user agent. Pasang keduanya di HTTP client Anda — cookie di header Cookie, dan user agent yang sama persis; ketidakcocokan user agent adalah penyebab tersering hasil ditolak.
OK|qa_validation_cookie=abc123;user_agent=Mozilla/5.0...
Error di res.php bentuknya sama seperti di in.php: satu kode ERROR_. Satu fungsi kecil ini merangkum ketiga bentuk respons menjadi status yang mudah dicabang di kode Anda:
ERROR_CODE
def parse_result(response_text):
if response_text == "CAPCHA_NOT_READY":
return {"status": "pending"}
if response_text.startswith("OK|"):
return {"status": "solved", "result": response_text.split("|", 1)[1]}
return {"status": "error", "error": response_text}
Endpoint saldo
Cek saldo lewat action getbalance. Balasannya satu angka desimal yang mewakili saldo Anda dalam USD — titik adalah pemisah desimal sesuai format API, bukan pemisah ribuan.
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=getbalance
1.234
balance = float(requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance"
}).text)
print(f"Balance: ${balance:.2f}")
Endpoint laporan
Melaporkan hasil membantu meningkatkan akurasi layanan. Gunakan reportgood setelah token diterima situs target dan reportbad setelah token ditolak; keduanya menjawab OK_REPORT_RECORDED. Melaporkan solve yang salah berpotensi mengembalikan saldo Anda untuk task tersebut.
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=reportgood&id=TASK_ID
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=reportbad&id=TASK_ID
Kode error yang umum
| Kode error | Artinya | Tindakan |
|---|---|---|
ERROR_WRONG_USER_KEY |
API key tidak valid | Verifikasi key Anda |
ERROR_KEY_DOES_NOT_EXIST |
Key tidak terdaftar | Periksa dashboard |
ERROR_ZERO_BALANCE |
Saldo tidak cukup | Tambah saldo |
ERROR_NO_SLOT_AVAILABLE |
Server sedang penuh | Coba lagi setelah 5 detik |
ERROR_CAPTCHA_UNSOLVABLE |
Tantangan terlalu sulit | Kirim ulang dengan CAPTCHA baru |
ERROR_BAD_DUPLICATES |
Task duplikat ditolak | Tunggu sebelum kirim ulang |
ERROR_WRONG_CAPTCHA_ID |
Task ID tidak valid | Periksa nilai task ID |
ERROR_EMPTY_ACTION |
Parameter action tidak ada |
Tambahkan action=get |
IP_BANNED |
Terlalu banyak request buruk | Perbaiki API key Anda; tunggu |
Cara paling praktis bukan menghafal setiap kode, melainkan mengelompokkannya berdasarkan reaksi yang tepat: (1) berhenti dan perbaiki konfigurasi untuk ERROR_WRONG_USER_KEY, ERROR_KEY_DOES_NOT_EXIST, ERROR_ZERO_BALANCE, dan IP_BANNED — percuma di-retry sebelum akar masalahnya beres; (2) coba ulang dengan jeda (backoff) untuk ERROR_NO_SLOT_AVAILABLE dan kondisi transien lain saat server sibuk; (3) kirim ulang task baru untuk ERROR_CAPTCHA_UNSOLVABLE yang menandakan tantangan spesifik itu gagal; dan (4) lanjut polling untuk CAPCHA_NOT_READY maupun ERROR_WRONG_CAPTCHA_ID yang cukup ditangani di dalam loop.
Contoh polling lengkap
Fungsi berikut menyatukan semuanya — submit, polling, dan penanganan respons — dalam satu solver generik. Jeda time.sleep(5) menjaga agar Anda tidak membanjiri res.php dengan polling terlalu rapat; 5 detik adalah titik awal yang aman, dan bisa diperbesar untuk task yang cenderung lama.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_captcha(submit_params, timeout=300):
"""Generic solver with proper response handling."""
submit_params["key"] = API_KEY
# Submit
resp = requests.get("https://ocr.captchaai.com/in.php", params=submit_params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit error: {resp.text}")
task_id = resp.text.split("|")[1]
# Poll
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id
})
parsed = parse_result(result.text)
if parsed["status"] == "pending":
continue
elif parsed["status"] == "solved":
return parsed["result"]
else:
raise Exception(f"Solve error: {parsed['error']}")
raise TimeoutError(f"Task {task_id} timed out after {timeout}s")
Pertanyaan Umum
Bagaimana cara cepat membedakan sukses, belum siap, dan error?
Cek tiga hal berurutan: apakah string persis sama dengan CAPCHA_NOT_READY (belum siap), apakah diawali OK| (sukses), dan jika keduanya tidak, perlakukan sebagai kode ERROR_. Fungsi parse_result di atas menerapkan urutan ini hanya dalam beberapa baris.
Berapa detik jeda ideal antar polling di res.php?
Mulai dari 5 detik. Polling lebih rapat hanya memperbanyak balasan CAPCHA_NOT_READY tanpa mempercepat hasil, dan berisiko memicu pembatasan laju permintaan. Untuk tipe yang lebih lambat, perbesar jeda secara bertahap.
Kenapa formatnya pakai pemisah pipa (|), bukan JSON?
Respons yang dibatasi pipa lebih ringkas dan lebih cepat diurai dibanding JSON, sehingga cocok untuk skrip shell maupun loop polling berfrekuensi tinggi. Untuk data terstruktur seperti GeeTest, bagian setelah OK| sendiri berisi pasangan key-value yang tinggal Anda pecah dengan koma lalu titik dua.
Bagaimana menangani error jaringan yang bukan dari API?
Bungkus panggilan API dalam try/except dan coba ulang pada ConnectionError atau Timeout. Masalah jaringan terpisah dari kode ERROR_ di atas; API-nya sendiri dirancang untuk ketersediaan tinggi, jadi kegagalan yang menetap biasanya menunjuk ke DNS, egress, atau konfigurasi TLS di sisi Anda.