How to Fix ERROR_CAPTCHA_UNSOLVABLE and Retry Safely

error_captcha_unsolvable - How to Fix ERROR_CAPTCHA_UNSOLVABLE and Retry Safely

Short answer: ERROR_CAPTCHA_UNSOLVABLE means the solver accepted your task, tried it, and could not produce an answer. Your key is fine, your setup is fine, and this one attempt failed. The documented fix is to submit a new task and retry, not to poll the same captcha ID again. This guide covers why re-polling never works, what actually causes the failure for each CAPTCHA type, and a retry loop that does not spin forever.

What the error means, and where it comes from

Unlike a key or parameter error, this one does not appear at submit time. Your /in.php call succeeded and handed back an ID. The failure surfaces later, when you poll /res.php for the result.

# Submit: this part worked, you got an ID back
curl "http://127.0.0.1:8080/in.php?key=YOUR_API_KEY&method=userrecaptcha&googlekey=YOUR_SITEKEY&pageurl=https://example.com/page-with-recaptcha"
OK|2122988149

# Poll: the attempt finished, and it finished badly
curl "http://127.0.0.1:8080/res.php?key=YOUR_API_KEY&action=get&id=2122988149"
ERROR_CAPTCHA_UNSOLVABLE

So the sequence tells you something useful. Everything up to and including submission was correct. The problem is in what you submitted, not in how you submitted it.

Why retrying the same ID never helps

Two reasons, and both are structural.

First, the attempt is over. The result for that ID is final, and polling it again does not start a second attempt. Second, results are readable once only. Once you have read a result off /res.php, that ID has nothing left to give you.

If your error handling loops on the same ID after a failure, it will loop until your timeout fires and then report a timeout, which sends you off debugging the wrong thing. Retry means calling /in.php again and getting a new ID. This is the opposite of CAPCHA_NOT_READY, where the correct move is to keep polling the ID you have.

What actually causes it, by CAPTCHA type

Image CAPTCHAs: the input is usually the problem

Image solving is the type where a genuinely unsolvable input is common, because you control the pixels. Check the file before blaming the solver.

  • Size limits. Over 600 kB or over 1000px in any dimension returns ERROR_TOO_BIG_CAPTCHA_FILESIZE rather than this error, but images close to those limits are frequently screenshots of a whole page.
  • You cropped the wrong region. A crop that includes the label, the border or half the next field is a much harder image than the CAPTCHA on its own.
  • You saved the placeholder. Fetching the image URL separately often returns a fresh, different CAPTCHA, or an expired one, or a 1×1 pixel. Capture the bytes the browser already has.
  • Format. A corrupt or unexpected format gives ERROR_INVALID_IMAGE, and a malformed data URI gives ERROR_INVALID_BASE64.

Save the exact bytes you are sending to disk and open them. Half of all image failures are visible in two seconds. The image CAPTCHA solver page lists the accepted input forms: a file path, a remote URL, or a data:image/png;base64, URI.

reCAPTCHA: stale sitekey or the wrong page URL

A sitekey read from cached page source, or copied from a staging environment, submits cleanly and then fails. So does a pageurl that does not match the page the widget is actually on, including a missing scheme or a redirect you did not follow.

Enterprise is the other frequent one. If the site runs reCAPTCHA Enterprise and you submit without the enterprise flag, the task is solved against the wrong configuration. Same for v3: pass version, and pass the site’s real action value rather than the default verify.

There is no minimum-score option in CapSkip, so if you are porting code from another service, drop any min_score or minScore parameter. It is not a cause of this error, but it will not do what you expect either.

Turnstile: challenge pages need more than a sitekey

Widget Turnstile takes a sitekey and a URL. A full challenge page also needs data (the cData value) and pagedata (chlPageData), both read from the page at that moment. Submit a challenge page as if it were a widget and you get a task the solver cannot complete. Both values are short-lived, so scrape them immediately before submitting.

GeeTest: an expired challenge is the usual answer

The gt value is static per site. The challenge value is single-use and expires in roughly a minute. If you fetch a challenge, do some other work, and then submit, it can already be dead. Fetch it immediately before the solve. Details are on the GeeTest solver page.

A retry loop that actually retries

Submit again, cap the attempts, and back off between them. Three attempts is a sensible ceiling: if a target fails three fresh submissions, the input is wrong and a fourth will not fix it.

# pip install capskip
import time
from capskip import CapSkip, ApiException

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

def solve_with_retry(sitekey, url, attempts=3):
    for n in range(attempts):
        try:
            # A fresh call means a fresh task, which is the fix.
            return solver.recaptcha(sitekey=sitekey, url=url)
        except ApiException as e:
            if "UNSOLVABLE" not in str(e) or n == attempts - 1:
                raise
            time.sleep(2 ** n)   # 1s, then 2s

token = solve_with_retry("YOUR_SITEKEY", "https://example.com/page-with-recaptcha")
print(token["code"][:40])

Note what it does not do: it does not retry ValidationException, because bad parameters fail identically every time, and it does not retry NetworkException, because that means the app is not reachable and every other target is about to fail too.

The same shape in Node.js:

// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

async function solveWithRetry(sitekey, url, attempts = 3) {
  for (let n = 0; n < attempts; n++) {
    try {
      return await solver.recaptcha(sitekey, url);
    } catch (e) {
      const last = n === attempts - 1;
      if (last || !String(e).includes('UNSOLVABLE')) throw e;
      await new Promise(r => setTimeout(r, 1000 * 2 ** n));
    }
  }
}

How to make it happen less often

  • Read the sitekey from the live DOM, not from a constant in your code. Sites rotate them.
  • Send the URL you are actually on, after redirects, with the scheme included.
  • Grab short-lived values last. GeeTest challenges and Turnstile cData go stale in under a minute.
  • Crop images tightly and send the bytes the page already loaded.
  • Log the captcha ID with every failure. It makes a pattern visible: one bad target versus a general problem.
  • Test against a known-good page first. If a page that normally works also fails, the problem is your environment, not the target.

Nearby error codes

These are the ones people confuse with an unsolvable task. They mean different things and want different fixes.

CodeMeansWhat to do
CAPCHA_NOT_READYStill working on itKeep polling the same ID
ERROR_INVALID_IMAGEFormat is wrong or the data is corruptRe-save the image and check the bytes
ERROR_TOO_BIG_CAPTCHA_FILESIZEOver 600 kB, or over 1000pxCrop to the CAPTCHA itself
ERROR_INVALID_BASE64The base64 payload will not decodeStrip the data URI prefix or fix the padding
ERROR_UPLOADNo image data arrivedCheck the field name and the request body
ERROR_GOOGLEKEYThe sitekey was rejected outrightRe-read it from the live page
ERROR_PAGEURLThe page URL is missing or malformedSend the full URL including the scheme

Every code the API can return, with its exact meaning, is in the API documentation.

Frequently asked questions

Does a failed solve cost me anything?

No. There is no per-solve charge and no balance, because the work happens on your own machine. Retrying costs you time and CPU, nothing else, which is why a three-attempt loop is reasonable here.

Can I get the result again after reading it once?

No. Results are readable once. Store the token the moment you receive it, and treat a second read of the same ID as a bug in your code rather than a way to recover.

Would a proxy fix an unsolvable image CAPTCHA?

It cannot. Proxies apply to reCAPTCHA, Turnstile and GeeTest only, not to image solving. An image task is judged entirely on the pixels you sent, so fix the crop instead.

Every task on one site fails. Is that still this error?

Usually it is one wrong constant applied everywhere: a stale sitekey, a missing enterprise flag, or a challenge page being submitted as a widget. Fix the target once and the whole set starts passing.

Summary

Treat error_captcha_unsolvable as a verdict on the task you submitted. Do not poll the old ID. Submit a fresh one, cap the attempts at three, and spend the debugging time on the inputs: the sitekey, the page URL, the crop, and anything short-lived you fetched too early.

The nice part of running a local captcha solver is that this is a cheap failure. No credit is burned, no rate limit is consumed, and you can reproduce the same task as many times as it takes to work out which value was wrong.