How to Fix CAPCHA_NOT_READY When Polling for a Result

capcha_not_ready - How to Fix CAPCHA_NOT_READY When Polling for a Result

Short answer: CAPCHA_NOT_READY is not an error. It is the API telling you the CAPTCHA is still being solved and you asked too early. There is nothing to fix in your request. You just need the right polling rhythm, and a loop that knows when to stop. This guide covers both, plus the read-once behaviour that turns a working script into a confusing one.

What CAPCHA_NOT_READY actually means

When you submit a CAPTCHA to /in.php you get back an ID, not an answer. Solving happens in the background. You then poll /res.php with that ID until the answer is ready.

Until it is, every poll returns the same string:

# Poll for the result. Note action=get and the id from /in.php.
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=CAPTCHA_ID"

# Still working:
CAPCHA_NOT_READY

# Done:
OK|03AGdBq26Sxo...

And yes, it is spelled CAPCHA, not CAPTCHA. That typo has been in the 2captcha API since the beginning. CapSkip is drop-in compatible with that API, so the misspelling is preserved deliberately. If it were corrected, every existing client library checking for the exact string would break. Match it exactly in your code.

Poll on the right schedule

Most people hit this constantly because they poll immediately after submitting. Different CAPTCHA types take very different amounts of time, so the first check should not happen at the same moment for all of them.

TypeWait before first checkThen retry every
Image / text1 second5 seconds
reCAPTCHA v215 to 20 seconds5 seconds
reCAPTCHA v310 to 15 seconds5 seconds
GeeTestabout 5 seconds5 seconds
Cloudflare Turnstileabout 5 seconds5 seconds

Polling faster than every 5 seconds does not make anything solve quicker. It just burns requests.

A polling loop that terminates

The shell version, showing the shape clearly:

# Submit, capture the id, give it a head start, then poll.
ID=$(curl -s "http://127.0.0.1:8080/in.php?key=YOUR_API_KEY&method=userrecaptcha&googlekey=YOUR_SITEKEY&pageurl=https://example.com" | cut -d'|' -f2)

sleep 15
while :; do
  RES=$(curl -s "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=$ID")
  [ "$RES" = "CAPCHA_NOT_READY" ] || break
  sleep 5
done
echo "$RES"

That loop has a flaw worth naming: it runs forever if something goes wrong upstream. In real code, cap it.

# pip install requests
import time
import requests

BASE = "http://127.0.0.1:8080"

def solve_recaptcha(sitekey, page_url, api_key="YOUR_API_KEY", timeout=180):
    task = requests.get(BASE + "/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    }).json()
    task_id = task["request"]

    time.sleep(15)                      # reCAPTCHA needs a head start
    deadline = time.monotonic() + timeout

    while time.monotonic() < deadline:
        res = requests.get(BASE + "/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
        }).text.strip()

        if res.startswith("OK|"):
            return res.split("|", 1)[1]

        # Anything that is not the pending string is terminal.
        if res != "CAPCHA_NOT_READY":
            raise RuntimeError(res or "empty response: already read, or bad id")

        time.sleep(5)

    raise TimeoutError("gave up after %ss" % timeout)

Three things make this safe: a deadline so it cannot hang, treating any non-pending string as terminal so real errors surface immediately, and reading the answer exactly once.

Or skip the loop entirely

If you are using one of the official SDKs, none of the above is your problem. Polling happens inside the call and you get the token back directly, so CAPCHA_NOT_READY never reaches your code.

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# Submit and poll happen inside this one call.
result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
)

print(result["code"])   # token, ready to inject
// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
const result = await solver.recaptcha('YOUR_SITEKEY', 'https://example.com/page-with-recaptcha');

console.log(result.code);
// composer require capskip/capskip
use CapSkip\CapSkip;

$solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]);
$result = $solver->recaptcha('YOUR_SITEKEY', 'https://example.com/page-with-recaptcha');

echo $result['code'];
// dotnet add package CapSkip
using CapSkip;

var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
var result = await solver.RecaptchaAsync("YOUR_SITEKEY", "https://example.com/page-with-recaptcha");

Console.WriteLine(result.Code);

Full method signatures for all four languages are on the CAPTCHA solving SDK page. The raw loop above is still what you want for Go, Java, Ruby, or anything without an official package.

The read-once trap

This is the one that wastes an afternoon. Each result can be read only once. Poll again after a successful read and you get an empty response, not the token you already had.

So an empty body does not mean “still working”. It means one of two things:

  • You already retrieved this result and threw it away
  • The ID does not exist, usually a mangled or truncated ID from parsing OK|ID

Store the token the moment you get it. Do not re-poll to “confirm” it.

When it is genuinely a problem

If CAPCHA_NOT_READY never resolves, the pending string is a symptom rather than the cause. Check these in order:

ResponseWhat it meansFix
ERROR_CAPTCHA_UNSOLVABLESolving was attempted and failedVerify the sitekey and pageurl are the live ones, then resubmit
ERROR_WRONG_ID_FORMATThe ID is not a valid integerYou are parsing OK|ID wrong. Split on the pipe, take field 2
ERROR_GOOGLEKEYThe sitekey was rejected at submit timeRe-read it from the live page, not from cached source
Empty bodyAlready read, or unknown IDStore the result on first read
Pending past 3 minutesThe solver is not running or not reachableConfirm the service is up on the configured port. See the setup guide

The full list of error strings is in the API documentation.

Frequently asked questions

Is CAPCHA_NOT_READY an error I should log?

Not as an error. It is the normal in-progress state and you will see it several times per solve. Log it at debug level if at all, or you will bury real failures under noise.

Does polling faster return the answer sooner?

No. Solving time is independent of how often you ask. Every 5 seconds is the documented interval and anything faster is wasted requests.

Why is it spelled CAPCHA and not CAPTCHA?

It is an old typo in the original 2captcha API that became part of the contract. CapSkip is drop-in compatible with that API, so the string is preserved exactly. Fixing the spelling would break every client that checks for it.

Can I get the pending state as JSON?

Yes. Add json=1 to the request and responses come back as an object with status and request fields instead of plain text. The pending string itself is unchanged.

Summary

Give the solve a head start before your first check, retry every 5 seconds, treat anything other than CAPCHA_NOT_READY as terminal, cap the loop with a deadline, and read the result exactly once. That is the whole pattern.

If you would rather not write the loop at all, CapSkip is a captcha solver that runs locally and ships SDKs for Python, Node.js, PHP and .NET that handle polling for you.