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.
| Type | Wait before first check | Then retry every |
|---|---|---|
| Image / text | 1 second | 5 seconds |
| reCAPTCHA v2 | 15 to 20 seconds | 5 seconds |
| reCAPTCHA v3 | 10 to 15 seconds | 5 seconds |
| GeeTest | about 5 seconds | 5 seconds |
| Cloudflare Turnstile | about 5 seconds | 5 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:
| Response | What it means | Fix |
|---|---|---|
ERROR_CAPTCHA_UNSOLVABLE | Solving was attempted and failed | Verify the sitekey and pageurl are the live ones, then resubmit |
ERROR_WRONG_ID_FORMAT | The ID is not a valid integer | You are parsing OK|ID wrong. Split on the pipe, take field 2 |
ERROR_GOOGLEKEY | The sitekey was rejected at submit time | Re-read it from the live page, not from cached source |
| Empty body | Already read, or unknown ID | Store the result on first read |
| Pending past 3 minutes | The solver is not running or not reachable | Confirm 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.
