Error Handling
Understand error codes and build resilient captcha-solving integrations.
A robust integration handles failures gracefully: it retries transient errors with backoff and fails fast on permanent ones. This page lists every error code the API can return and how to react to each.
Error response shape
When a request fails, the API returns a JSON body with a numeric code, a human-readable msg, and—for rate or capacity errors—a retryAfterSec hint telling you how long to wait before retrying.
{
"code": 16,
"msg": "RATE_LIMITED",
"retryAfterSec": 5
}Error codes
| Code | Name | Meaning | What to do |
|---|---|---|---|
| 1 | KEY_DOES_NOT_EXIST | The API key is missing, malformed, or invalid. | Fix the key. Do not retry. |
| 2 | NO_SLOT_AVAILABLE | No worker slot is currently free for your account. | Retry after a short backoff. |
| 3 | ZERO_BALANCE | Account balance is exhausted. | Top up credits. Do not retry. |
| 10 | ERROR_BAD_PARAMETERS | The task payload is invalid or incomplete. | Fix the request. Do not retry. |
| 12 | ERROR_CAPTCHA_UNSOLVABLE | The captcha could not be solved. | Retry, or resubmit with cleaner input. |
| 14 | PLAN_EXPIRED | Your subscription plan has expired. | Renew the plan. Do not retry. |
| 15 | PLAN_INACTIVE | Your plan is not active. | Activate the plan. Do not retry. |
| 16 | RATE_LIMITED | Too many requests in a short window. | Back off, respect retryAfterSec. |
| 17 | DAILY_LIMIT_EXCEEDED | Daily request quota reached. | Retry later (often next day). |
| 18 | QUOTA_LIMIT_EXCEEDED | Plan quota reached. | Retry later or upgrade the plan. |
| 21 | SERVICE_UNAVAILABLE | The service is temporarily unavailable. | Back off, respect retryAfterSec. |
Retry strategy
Group errors into three buckets: retryable, permanent, and input-related. Only retry the first bucket, and always honor retryAfterSec when it is present.
- Retryable with backoff:
2,16,17,18,21. These are transient (rate, quota, or capacity). WaitretryAfterSecif provided, otherwise use exponential backoff with jitter. - Permanent — fix your account or key:
1,3,14,15. Retrying will not help; surface the error to the operator. - Bad request:
10. Fix thetaskpayload before resending. - Unsolvable:
12. Retry a few times, or resubmit with a clearer image or corrected parameters.
Never retry permanent errors in a tight loop. Doing so wastes requests, can trigger RATE_LIMITED (code 16), and delays surfacing the real problem (an expired plan or empty balance).
Async flow with backoff (Python)
The example below submits a task with POST /createTask, then polls POST /getTaskResult until the status is ready. It treats 16, 17, 18, 21, and 2 as retryable and respects retryAfterSec.
import time
import random
import requests
BASE_URL = "https://api.nocaptchaai.com"
API_KEY = "YOUR_API_KEY"
RETRYABLE = {2, 16, 17, 18, 21}
def post(path, payload):
resp = requests.post(f"{BASE_URL}{path}", json=payload, timeout=30)
data = resp.json()
code = data.get("code")
if code in RETRYABLE:
wait = data.get("retryAfterSec")
raise RetryableError(wait)
if code: # non-zero code that is not retryable
raise RuntimeError(f"{code}: {data.get('msg')}")
return data
class RetryableError(Exception):
def __init__(self, retry_after_sec=None):
self.retry_after_sec = retry_after_sec
def with_backoff(fn, max_attempts=6):
for attempt in range(max_attempts):
try:
return fn()
except RetryableError as err:
if attempt == max_attempts - 1:
raise
wait = err.retry_after_sec
if wait is None:
wait = min(2 ** attempt, 30) + random.random()
time.sleep(wait)
raise RuntimeError("exceeded max retry attempts")
def solve(task):
created = with_backoff(lambda: post("/createTask", {
"clientKey": API_KEY,
"task": task,
}))
task_id = created["taskId"]
while True:
result = with_backoff(lambda: post("/getTaskResult", {
"clientKey": API_KEY,
"taskId": task_id,
}))
status = result.get("status")
if status == "ready":
return result["solution"]
time.sleep(2) # still processing; poll again
if __name__ == "__main__":
solution = solve({"type": "ImageToTextTask", "body": "BASE64_IMAGE"})
print(solution)Cap your total polling time and number of attempts so a stuck task cannot block your worker indefinitely. A few seconds between polls keeps you well under the rate limit.