Bắt Đầu

Giải thích về định dạng phản hồi API CaptchaAI

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ại if/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ự |.

Hướng dẫn liên quan

Os comentários estão desativados para este artigo.