Gửi task xong, bạn sẽ nhận về một trong bốn dạng phản hồi từ CaptchaAI: OK|TASK_ID khi gửi thành công, CAPCHA_NOT_READY khi task còn đang xử lý, OK|... kèm token hoặc dữ liệu khi giải xong, và ERROR_CODE khi có vấn đề. Không có gì phức tạp hơn thế — CaptchaAI dùng định dạng text đơn giản, phân tách bằng dấu |, thay vì bọc mọi thứ trong JSON. Bài này liệt kê từng dạng phản hồi cụ thể theo từng loại CAPTCHA, kèm code parse chạy được ngay bằng Python và JavaScript.
| Dạng phản hồi | Xuất hiện ở | Ý nghĩa |
|---|---|---|
OK\|TASK_ID |
in.php |
Gửi task thành công, đây là ID để poll kết quả |
CAPCHA_NOT_READY |
res.php |
Task còn đang xử lý, poll lại sau |
OK\|<payload> |
res.php |
Đã giải xong; phần sau OK\| là token, text OCR hoặc dữ liệu GeeTest |
ERROR_CODE |
cả hai endpoint | Có lỗi; đối chiếu bảng mã lỗi bên dưới |
Gửi task tới endpoint in.php
Phản hồi thành công
OK|TASK_ID
Ví dụ: OK|73548291
Phản hồi lỗi
ERROR_CODE
Ví dụ: ERROR_WRONG_USER_KEY
Parse trong code
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}`);
}
Poll kết quả tại endpoint res.php
Mẹo: dùng hàm
parse_result()bên dưới làm điểm xử lý duy nhất, đỡ viết lạiif/elseở từng nơi gọi API.
Sau khi có task_id, gọi res.php lặp lại mỗi vài giây. Trước khi có kết quả cuối, bạn nhận:
CAPCHA_NOT_READY
nghĩa là task vẫn đang xử lý — đợi 5 giây rồi poll lại, không cần retry nhanh hơn.
Các dạng thành công theo loại CAPTCHA
Payload sau OK| khác nhau tùy loại CAPTCHA. Bốn trường hợp thường gặp:
CAPTCHA dạng token — reCAPTCHA, Turnstile, hCaptcha và tương tự:
OK|03AGdBq24PBCbw...long_token_string
Image/OCR — phần sau OK| chính là text nhận dạng được từ ảnh:
OK|abc123
GeeTest — ba trường cách nhau bằng dấu phẩy, mỗi trường là cặp khóa:giá trị:
OK|challenge:abc123,validate:def456,seccode:ghi789
Parse ba trường này như sau:
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 — trả về cặp giá trị cookie phiên và user agent:
OK|qa_session_cookie=abc123;user_agent=Mozilla/5.0...
Nếu không khớp bất kỳ dạng nào ở trên, bạn nhận một ERROR_CODE như sau:
ERROR_CODE
Một hàm parse dùng chung cho tất cả
Gom mọi logic ở trên vào một hàm duy nhất, dùng được cho cả in.php lẫn res.php:
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 kiểm tra số dư
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=getbalance
Phản hồi:
1.234
Số thập phân, không bọc trong OK| — là số dư tài khoản tính bằng USD.
balance = float(requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance"
}).text)
print(f"Balance: ${balance:.2f}")
Nên gọi định kỳ khi chạy nhiều thread song song trên gói BASIC ($15/tháng, 5 thread) trở lên — phát hiện sớm ERROR_ZERO_BALANCE trước khi task bị dừng hàng loạt.
Endpoint báo cáo kết quả
Hai endpoint cho hai chiều phản hồi, cùng trả về OK_REPORT_RECORDED khi ghi nhận thành công.
Báo cáo giải đúng:
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=reportgood&id=TASK_ID
Báo cáo giải sai:
GET https://ocr.captchaai.com/res.php?key=API_KEY&action=reportbad&id=TASK_ID
Báo cáo token/text giải sai giúp cải thiện độ chính xác cho các lần sau, và trong một số trường hợp có thể được ghi có lại vào số dư của bạn.
Mã lỗi thường gặp
| Mã lỗi | Ý nghĩa | Cách xử lý |
|---|---|---|
ERROR_WRONG_USER_KEY |
API key không hợp lệ | Kiểm tra lại key |
ERROR_KEY_DOES_NOT_EXIST |
Key chưa được đăng ký | Kiểm tra dashboard |
ERROR_ZERO_BALANCE |
Không đủ số dư | Nạp thêm số dư |
ERROR_NO_SLOT_AVAILABLE |
Server đang hết công suất | Thử lại sau 5 giây |
ERROR_CAPTCHA_UNSOLVABLE |
Thử thách quá khó | Gửi lại CAPTCHA mới |
ERROR_BAD_DUPLICATES |
Task trùng lặp bị từ chối | Đợi trước khi gửi lại |
ERROR_WRONG_CAPTCHA_ID |
Task ID không hợp lệ | Kiểm tra lại giá trị ID |
ERROR_EMPTY_ACTION |
Thiếu tham số action |
Thêm action=get |
IP_BANNED |
Quá nhiều request lỗi | Sửa API key; đợi trước khi gọi lại |
Nhóm mã lỗi theo hành động cần làm sẽ dễ viết logic xử lý hơn bắt từng mã riêng lẻ:
| Nhóm lỗi | Ví dụ mã lỗi | Nên làm gì |
|---|---|---|
| Auth / tài khoản | ERROR_WRONG_USER_KEY, ERROR_KEY_DOES_NOT_EXIST, ERROR_ZERO_BALANCE, IP_BANNED |
Dừng lại, sửa cấu hình hoặc nạp số dư. Không retry. |
| Tham số | ERROR_BAD_PARAMETERS, ERROR_PAGEURL, ERROR_GOOGLEKEY |
Dừng lại, sửa request body. |
| Tạm thời | ERROR_NO_SLOT_AVAILABLE, ERROR_TOO_MUCH_REQUESTS, HTTP 429/5xx |
Backoff rồi retry. |
| Theo từng task | ERROR_CAPTCHA_UNSOLVABLE, ERROR_BAD_TOKEN, ERROR_PROXY_CONNECTION_FAILED |
Gửi lại một task mới. |
| Polling | CAPCHA_NOT_READY, ERROR_WRONG_CAPTCHA_ID |
Poll tiếp, hoặc kiểm tra lại task ID. |
Xem đầy đủ mã lỗi và cách xử lý tại tham khảo mã lỗi CaptchaAI.
Ví dụ polling hoàn chỉnh
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")
Áp dụng thực tế: parse phản hồi trong pipeline QA
Tình huống quen thuộc với đội automation/QA ở công ty outsourcing tại TP.HCM hay Hà Nội: job theo dõi giá công khai trên Shopee hoặc Tiki, chạy song song nhiều thread mỗi ngày. Cách chắc ăn là để parse_result() ở trên làm điểm tập trung duy nhất quyết định pending / solved / error, thay vì rải rác if resp.text.startswith(...) trong từng script con. Log ghi ERROR_ZERO_BALANCE hàng loạt thường là dấu hiệu cần nâng gói (BASIC 5 thread lên STANDARD $30/tháng, 15 thread), không phải lỗi code. Giới hạn thu thập ở nguồn công khai, ghi log đầy đủ — vừa dễ debug, vừa phù hợp Nghị định 13/2023/NĐ-CP về tối thiểu hóa dữ liệu.
Câu hỏi thường gặp
Tại sao CaptchaAI dùng dấu | thay vì JSON?
Để tối ưu tốc độ và độ đơn giản. Phản hồi phân tách bằng | nhẹ và parse nhanh hơn JSON, hữu ích khi gọi hàng nghìn request mỗi ngày. Với dữ liệu có cấu trúc như GeeTest, phần sau OK| vẫn chứa các cặp khóa-giá trị để tách ra dễ dàng.
CAPCHA_NOT_READY có tính là lỗi không?
Không. Đây là trạng thái "đang xử lý" — code nên coi nó là tín hiệu chờ và poll lại, không phải raise exception hay retry gấp. Hàm parse_result() ở trên map nó riêng vào status: pending, tách khỏi nhánh error.
Gặp ERROR_ZERO_BALANCE giữa chừng job đang chạy thì làm gì?
Dừng gửi task mới ngay — task đã gửi trước vẫn xử lý bình thường. Nạp thêm số dư rồi chạy tiếp từ điểm dừng, không cần gửi lại task đã hoàn tất.
Nên poll res.php bao lâu một lần?
5 giây một lần là hợp lý, như trong ví dụ polling ở trên — poll nhanh hơn không rút ngắn thời gian giải, chỉ tốn thêm request. Đặt timeout tổng (300 giây như trong solve_captcha()) để job tự dừng thay vì treo vô thời hạn.
Token reCAPTCHA dài tối đa bao nhiêu ký tự?
Khoảng 500 ký tự. Luôn dùng split("|", 1) (giới hạn 1 lần tách) khi parse, để không lỡ cắt luôn token nếu bản thân token chứa ký tự |.