API
Captcha API Error Codes: What Each Means and What It Costs
ERROR_CAPTCHA_UNSOLVABLE, ERROR_ZERO_BALANCE, ERROR_KEY_DOES_NOT_EXIST and the rest: what each captcha API error means, whether to retry, and what it costs.
4 min readPublished Updated
Every error from a CAPTCHA-solving API answers three questions: what went wrong, whether trying
again helps, and whether it cost you anything. This guide groups ZeroCaptcha’s error codes by
those questions, so your code can handle each group the same way. The codes are the ones clients
of the createTask format already know; the REST API uses lowercase equivalents with the same
meaning.
How errors arrive
In the compatible format, every reply is HTTP 200, and an error looks like this:
{ "errorId": 1, "errorCode": "ERROR_ZERO_BALANCE", "errorDescription": "Your balance cannot cover this task. Add funds and try again."}Branch on errorCode, never on the description, which may be reworded. The REST API answers with
an HTTP error status and an RFC 9457 problem document whose code field plays the same role. The
2Captcha format puts the code in place of the result, such as ERROR_ZERO_BALANCE.
Group 1: wait and retry
These say “not now”. Retry the same request after the time in the Retry-After header, or after a
short pause that doubles each time. For createTask, keep the same Idempotency-Key so a retry
never makes a second task.
| Code | Meaning |
|---|---|
ERROR_RATE_LIMIT |
Too many getTaskResult or getBalance calls; poll less often |
ERROR_NO_SLOT_AVAILABLE |
Your share of the queue is full for a moment |
ERROR_SERVICE_UNAVAILABLE |
The API could not serve the request just now |
ERROR_IDEMPOTENCY_KEY_IN_USE |
The first request with this key is still being served |
HTTP 429 and any 5xx status belong here too. Rate limits and concurrency covers pacing in detail.
Group 2: fix the request or the account
Retrying these unchanged gets the same answer. Each costs nothing, because no task was created.
| Code | What to do |
|---|---|
ERROR_KEY_DOES_NOT_EXIST |
The key is wrong or incomplete; copy it again from the dashboard |
ERROR_KEY_REVOKED |
The key was revoked, or its rotation overlap ended; use the new key |
ERROR_IP_NOT_ALLOWED |
Add this server’s address to the key’s allowlist |
ERROR_ACCESS_DENIED |
The key lacks the tasks or balance scope |
ERROR_ZERO_BALANCE |
Add funds, then send the task again |
ERROR_SPEND_CAP_REACHED |
The key’s daily cap is reached; raise it or wait for 00:00 UTC |
ERROR_TASK_ABSENT |
The body has no task or no type |
ERROR_TASK_NOT_SUPPORTED |
The task type is not one ZeroCaptcha solves |
ERROR_INVALID_TASK_DATA |
A field is missing or invalid; the description names it |
ERROR_IDEMPOTENCY_KEY_REUSED |
This key was used for a different task; use a new key |
ERROR_DOMAIN_BLOCKED |
The site is on the blocklist; do not send tasks for it |
ERROR_ACCOUNT_SUSPENDED |
The account is suspended; appeal from the dashboard |
In the 2Captcha format, ERROR_PAGEURL means pageurl is missing or not a public page, and
ERROR_PROXY_FORMAT means the proxy is not in a supported form.
Secure your captcha API keys explains allowlists, scopes and
spend caps.
Group 3: the task ran and failed
These come from getTaskResult after the task has run. The price held for the task goes back to
your balance at once: nothing is charged.
| Code | Meaning |
|---|---|
ERROR_CAPTCHA_UNSOLVABLE |
Every attempt to solve the challenge failed |
ERROR_TASK_TIMEOUT |
The task was not solved before its deadline |
ERROR_PROXY_NOT_ALLOWED |
The proxy points at a private or reserved address, such as 10.0.0.0/8 |
ERROR_BAD_PROXY |
The same, in the 2Captcha format |
A new task may succeed. If ERROR_CAPTCHA_UNSOLVABLE repeats on the same page, check the sitekey,
the URL, and the action and cData first: see Find a Cloudflare Turnstile sitekey.
Group 4: solved, but too late
ERROR_TOKEN_EXPIRED means the task was solved and charged, but you read it after its token had
expired. A Turnstile token works once, for 300 seconds. This is the one error that costs money,
and the fix is in your pipeline: use each token as soon as it is ready.
A handler in a few lines
RETRY = {"ERROR_RATE_LIMIT", "ERROR_NO_SLOT_AVAILABLE", "ERROR_SERVICE_UNAVAILABLE", "ERROR_IDEMPOTENCY_KEY_IN_USE"}TASK_FAILED = {"ERROR_CAPTCHA_UNSOLVABLE", "ERROR_TASK_TIMEOUT"}
def classify(code: str) -> str: if code in RETRY: return "retry-same-request" if code in TASK_FAILED: return "new-task" # nothing was charged if code == "ERROR_TOKEN_EXPIRED": return "new-task-faster" # charged; use tokens sooner return "stop-and-fix" # nothing was chargedThe full list
Every code in all three formats, with its HTTP status, whether a retry helps, what to do and what it costs, is in the errors reference, and each code has its own anchor, such as ERROR_ZERO_BALANCE. The Cloudflare Turnstile solver page shows the calls these errors come from.