DEV Community

Bob
Bob

Posted on Originally published at capbypass.pro

Captcha solver API in Python: create, poll, time out

Your script calls createTask, gets a taskId, then either hammers getTaskResult every 100 ms or sleeps a flat 10 s and hands the target an expired token. Both come from polling blind. On CapBypass, the median ReCaptchaV3EnterpriseTaskProxyLess token was ready in 0.4 s over the last 7 days, so a 10 s sleep wastes 96% of the wait. This post sets the poll interval and timeout from measured solve times, then shows the loop in curl, Python and TypeScript with the errors that should stop it.


Two calls, three states

A captcha solver API is two HTTP calls against https://api.capbypass.pro:

  1. POST /createTask with your clientKey and a task object. The reply is {"errorId": 0, "taskId": "..."}. A taskId only means the task was queued.
  2. POST /getTaskResult with the same clientKey and the taskId, repeated until status changes.

getTaskResult answers with one of three states:

status errorId what to do
processing 0 wait, poll again
ready 0 read solution, use it now
failed 1 read errorCode, decide retry or stop

The task and its result live for 10 minutes. After that, getTaskResult returns ERROR_TASK_NOT_FOUND, so a worker that crashes mid-poll and resumes later cannot pick the result back up. The full request and response schema is in the API reference. For the model behind it (what happens server-side, token vs cookie captchas), see how a captcha-solving API works.


Set the interval from real solve times

Solve time depends on the task type far more than on your code. These are successful solves on CapBypass over the last 7 days, from our usage logs:

task type p50 p95
ReCaptchaV3EnterpriseTaskProxyLess 0.4 s 2.0 s
CaptchaFoxTask 0.9 s 5.3 s

Challenge captchas sit further out: GeeTest had a median of 14 s in the same window (details in the GeeTest solver post).

Poll schedule against measured solve times: ticks at 0, 1, 2, 4, 6 and 9 seconds; Enterprise reCAPTCHA v3 p50 0.4 s to p95 2.0 s and CaptchaFox p50 0.9 s to p95 5.3 s, both ready by the fifth poll at 6 s

Three rules follow from that table:

  • Poll once right after createTask, then back off. 1 s, 1 s, 2 s, 2 s, 3 s, 3 s, 4 s, 4 s, then every 5 s. 95% of the token solves above are ready by the fifth poll (6 s), and slow tasks stop costing you a request per second.
  • Time out at 120 s. That covers the slow tail of every active task type. Past it, create a new task instead of waiting.
  • Don't sleep before the first poll. At a 0.4 s median, the first poll often already returns ready.

The official SDKs use exactly this schedule and a 120 s default timeout, so client.solve() is the right call unless you need control over the errors.


The loop in curl

Create the task, keep the taskId, poll:

TASK_ID=$(curl -s https://api.capbypass.pro/createTask \
  -H 'Content-Type: application/json' \
  -d '{
    "clientKey": "'"$CAPBYPASS_API_KEY"'",
    "task": {
      "type": "ReCaptchaV3EnterpriseTaskProxyLess",
      "websiteURL": "https://example.com/login",
      "websiteKey": "6Lc...your-site-key",
      "pageAction": "login"
    }
  }' | jq -r .taskId)

for delay in 0 1 1 2 2 3 3 4 4 5; do
  sleep "$delay"
  RES=$(curl -s https://api.capbypass.pro/getTaskResult \
    -H 'Content-Type: application/json' \
    -d '{"clientKey":"'"$CAPBYPASS_API_KEY"'","taskId":"'"$TASK_ID"'"}')
  STATUS=$(echo "$RES" | jq -r .status)
  [ "$STATUS" != "processing" ] && { echo "$RES"; break; }
done
Enter fullscreen mode Exit fullscreen mode

The loop stops after 10 polls (about 25 s), plenty for token captchas. A finished task prints {"errorId":0,"status":"ready","solution":{"gRecaptchaResponse":"03AGdBq2..."}}. A failed one prints errorCode and errorDescription.


The loop in Python, with error handling

The raw loop is worth writing yourself when you need to react to specific errorCode values. Install nothing beyond requests:

import os
import time
import requests

API = "https://api.capbypass.pro"
KEY = os.environ["CAPBYPASS_API_KEY"]
RETRY_CODES = {"ERROR_TASK_QUEUE_FULL", "ERROR_CAPTCHA_UNSOLVABLE", "ERROR_TIMEOUT", "ERROR_WORKER_CRASHED"}


def solve(task: dict, timeout: float = 120, attempts: int = 2) -> dict:
    for attempt in range(attempts):
        created = requests.post(f"{API}/createTask", json={"clientKey": KEY, "task": task}, timeout=30).json()
        if created["errorId"]:
            raise RuntimeError(f'{created["errorCode"]}: {created["errorDescription"]}')

        deadline, n = time.monotonic() + timeout, 0
        while time.monotonic() < deadline:
            res = requests.post(f"{API}/getTaskResult", json={"clientKey": KEY, "taskId": created["taskId"]}, timeout=30).json()
            if res.get("status") == "ready":
                return res["solution"]
            if res.get("status") == "failed" or res["errorId"]:
                if res.get("errorCode") in RETRY_CODES and attempt + 1 < attempts:
                    time.sleep(3)
                    break
                raise RuntimeError(f'{res["errorCode"]}: {res.get("errorDescription")}')
            n += 1
            time.sleep(min(5, (n + 1) // 2))
        else:
            raise TimeoutError(f"no result after {timeout}s")
    raise RuntimeError("retries exhausted")


solution = solve({
    "type": "ReCaptchaV3EnterpriseTaskProxyLess",
    "websiteURL": "https://example.com/login",
    "websiteKey": "6Lc...your-site-key",
    "pageAction": "login",
})
print(solution["gRecaptchaResponse"][:40])
Enter fullscreen mode Exit fullscreen mode

If you don't need per-code handling, the Python SDK does the same in one call: pip install capbypass-sdk, then CapBypass().solve(task, timeout=120).


The same with the TypeScript SDK

npm i @capbypass/sdk. solve() creates, polls on the same back-off and throws on failure or timeout:

import { CapBypassClient, TaskType, SolverError, TimeoutError } from '@capbypass/sdk';

const client = new CapBypassClient({ apiKey: process.env.CAPBYPASS_API_KEY });

const task = {
  type: TaskType.RECAPTCHA_V3_ENTERPRISE_PROXYLESS,
  websiteURL: 'https://example.com/login',
  websiteKey: '6Lc...your-site-key',
  pageAction: 'login',
};

async function getToken(): Promise<string> {
  for (let attempt = 1; ; attempt++) {
    try {
      const solution = await client.solve(task, 120);
      return solution.gRecaptchaResponse;
    } catch (err) {
      // failed tasks are not billed, one retry is free
      if ((err instanceof SolverError || err instanceof TimeoutError) && attempt < 2) continue;
      throw err;
    }
  }
}

console.log((await getToken()).slice(0, 40));
Enter fullscreen mode Exit fullscreen mode

The TypeScript SDK reference lists every task type constant and error class.


Bonus: +5% credits on every top-up

New to CapBypass? Apply code WELCOME_2026 at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the top-up page and put it toward your first createTask calls.


Which errors to retry

A failed task costs nothing: every failed task in the last 7 days was billed $0. So the question is never "can I afford a retry" but "will a retry help".

  • ERROR_TASK_QUEUE_FULL: capacity is momentarily full. It was 3.7% of ReCaptchaV3EnterpriseTaskProxyLess tasks last week. Wait 2-3 s and create a new task.
  • ERROR_CAPTCHA_UNSOLVABLE, ERROR_TIMEOUT, ERROR_WORKER_CRASHED: retry once with a new task. A second failure on the same input usually means the input is wrong (wrong websiteKey, wrong page URL).
  • ERROR_PROXY_CONNECTION_FAILED, ERROR_PROXY_BANNED: your proxy, not the solver. Swap the proxy or move to the ProxyLess type.
  • ERROR_INVALID_TASK_DATA, ERROR_KEY_DOES_NOT_EXIST, ERROR_ZERO_BALANCE: stop. Retrying sends the same bad request.
  • ERROR_TASK_NOT_FOUND while polling: the 10 minute window passed or the taskId belongs to another key.

Captcha token expired: use the result right away

A ready result stays readable for 10 minutes, but the token inside it does not live that long. A reCAPTCHA v3 token is valid for 2 minutes after it is minted. If your code solves first and submits after a slow page load, a queue, or a human review step, the target answers with an expired-token error even though the solve worked.

Two habits fix it:

  • Create the task when you are ready to submit, not at the start of a long job.
  • Never cache a token for reuse. Each token is single-use, so the second submit fails even inside the 2 minutes.

If the token arrives fresh and the target still rejects it, the problem is the input, usually the action name. Fixing a reCAPTCHA action mismatch walks through that case, and reCAPTCHA error codes covers the rest.


Originally published on capbypass.pro. CapBypass is an AI-powered captcha-solving API for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.

Top comments (0)