Error Handling
How AnySolver returns errors, what each errorId means, and when to retry.
Every endpoint returns the same envelope. Branch on errorId, surface errorCode to logs, and use errorDescription for human-readable messages.
Error response
{
"errorId": 1,
"errorCode": "CAPTCHA_UNSOLVABLE",
"errorDescription": "The captcha could not be solved. Check your parameters or try again."
}| Field | Type | Description |
|---|---|---|
errorId | number | Error category. See Error IDs. |
errorCode | string | Machine-readable code. Use this for branching. |
errorDescription | string | Human-readable message. May be the unified text or a forwarded provider message. |
HTTP status is always 200
Every endpoint returns HTTP 200 even on error. Branch on the JSON errorId, never the HTTP status.
Error IDs
errorId | Category | What caused it |
|---|---|---|
0 | Success | The request succeeded. |
1 | External | The provider responded with the error (bad input, sitekey, captcha unsolvable, etc.). The error originated outside AnySolver. |
2 | Internal | The error was caused by AnySolver itself. |
Full error code reference
Every code the API can return:
| errorId | errorCode | errorDescription |
|---|---|---|
| 1 | CAPTCHA_UNSOLVABLE | The captcha could not be solved. Check your parameters or try again. |
| 1 | DOMAIN_BANNED | This domain is not allowed. Captchas from certain domains are forbidden. |
| 1 | ERROR_KEY_DOES_NOT_EXIST | Invalid or missing API key. Check your dashboard for the correct key. |
| 1 | ERROR_USER_BANNED | Account suspended due to improper API use. Contact support. |
| 1 | ERROR_ZERO_BALANCE | Insufficient account balance. Top up and retry. |
| 1 | IMAGE_CORRUPTED | Image is corrupted or unsupported format. Use JPG, PNG, or GIF. |
| 1 | IMAGE_TOO_LARGE | Image exceeds size limit. Reduce resolution or compress the file. |
| 1 | IMAGE_TOO_SMALL | Image is too small (< 100 bytes). Check that the file is valid. |
| 2 | INTERNAL_ERROR | An internal error occurred. Please try again later. |
| 1 | INVALID_DOMAIN | Domain mismatch or not allowed. Ensure domain matches sitekey. |
| 1 | INVALID_SITEKEY | Invalid websiteKey. Check the sitekey in the page source. |
| 1 | INVALID_TASK_DATA | Invalid or missing task parameters. Check request structure. |
| 1 | INVALID_USERAGENT | Invalid or expired User-Agent. Update to a current browser UA. |
| 1 | INVALID_WEBSITEURL | Invalid websiteURL format. Use full URL with http:// or https://. |
| 1 | NO_PROVIDERS_AVAILABLE | No providers available for this task type. Check task type or provider configuration. |
| 2 | PROVIDER_BALANCE_ERROR | Provider returned error when fetching balance: {errorDescription} |
| 1 | PROVIDER_BALANCE_NOT_SUPPORTED | Provider {provider} does not support balance checking. |
| 2 | PROVIDER_GLOBAL_LIMIT_REACHED | Provider usage limit reached. See https://anysolver.com/docs/errors/provider-limits for details. |
| 2 | PROVIDER_RATE_LIMITED | Provider rate limit reached. See https://anysolver.com/docs/errors/provider-limits for details. |
| 2 | PROVIDER_RESPONSE_MALFORMED | Provider response is malformed or missing required fields. Description varies on the cause of the error. |
| 1 | PROVIDER_TASK_NOT_SUPPORTED | Provider {provider} does not support task type {taskType}. |
| 2 | PROVIDER_UNREACHABLE | Could not connect to the solving provider. Try again later. |
| 1 | PROXY_AUTH_FAILED | Proxy authentication failed. Check proxyLogin and proxyPassword. |
| 1 | PROXY_BANNED | Proxy IP is banned by the target service. Use a different proxy. |
| 1 | PROXY_CONNECTION_FAILED | Failed to connect to proxy. Check IP, port, and availability. |
| 1 | PROXY_INCOMPATIBLE | Proxy is incompatible (transparent, no SSL, or wrong protocol version). Try HTTP instead of SOCKS. |
| 1 | PROXY_MISSING | Proxy required but not provided. Add proxyType, proxyAddress, proxyPort. |
| 1 | RATE_LIMITED | Request rate limit exceeded. |
| 1 | SERVICE_UNAVAILABLE | The provider is temporarily unavailable or out of capacity. Try again later. |
| 1 | TASK_NOT_FOUND | Task ID not found or expired. Request the result within 5 minutes. |
| 1 | TASK_NOT_SUPPORTED | The task type is not supported. Check the type parameter. |
| 1 | TASK_TIMEOUT | The task timed out. Try with a different proxy or retry later. |
| 1 | TOKEN_EXPIRED | The token or session has expired. Obtain a new token and retry. |
| 2 | TRANSFORMATION_ERROR | Failed to transform request. |
| 1 | UNKNOWN_PROVIDER_ERROR | Unknown provider error. This description is not static and varies on the cause of the error. |
| 2 | VALIDATION_ERROR | The validation of the request failed. Check if the task schema matches the documentation. |
Retries and idempotency
createTask is not idempotent: every call charges the estimated cost up front. If your retry succeeds you get charged again, even if the original request also went through. Build retry logic accordingly:
errorId === 2(internal): safe to retry with exponential backoff. The estimated cost is refunded automatically.errorId === 1anderrorCode === "RATE_LIMITED": also safe to retry, with a small jittered delay. Refunded automatically.errorId === 1for everything else (INVALID_TASK_DATA,ERROR_KEY_DOES_NOT_EXIST, etc.): fix the input first. Retrying without changes will fail the same way.
getTaskResult never charges and never mutates state, so retry it as often as you need.
Example handler
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function postWithRetry(url, body, attempt = 0) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
if (data.errorId === 0) return data;
// Internal errors: retry with exponential backoff (max 5 attempts).
if (data.errorId === 2 && attempt < 5) {
await sleep(2 ** attempt * 500);
return postWithRetry(url, body, attempt + 1);
}
// External errors: surface to the caller. The caller decides whether to retry.
throw Object.assign(new Error(data.errorDescription ?? data.errorCode), {
errorId: data.errorId,
errorCode: data.errorCode,
});
}