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 | CHALLENGE_NOT_PRESENT | The requested challenge was not present. |
| 1 | DOMAIN_BANNED | This website is restricted by the solving provider used for this task, not by AnySolver. Contact that provider to request access. |
| 2 | ERROR_IP_NOT_ALLOWED | The provider rejected the request because AnySolver's server IP is not whitelisted for this key. |
| 1 | ERROR_KEY_DOES_NOT_EXIST | Invalid or missing API key. Check your dashboard for the correct key. |
| 2 | ERROR_PROVIDER_ZERO_BALANCE | The provider account behind this request is out of balance. The task was refunded and rerouted where possible. See https://anysolver.com/docs/errors/provider-limits for details. |
| 1 | ERROR_USER_BANNED | Account suspended due to improper API use. Contact support. |
| 1 | ERROR_ZERO_BALANCE | Insufficient AnySolver 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.
Auto retry & fallback
Instead of building your own retry loop, let AnySolver recover inside a single task. Enable them per request with settings.retry and settings.fallback: auto retry re-tries the same provider, auto fallback moves to the next provider in the routing candidate list. See Routing Strategies for the full behavior.
A recovered task succeeds with errorId: 0; you are charged for the provider that ultimately solved it. With settings.statistics enabled, the retry object on getTaskResult lists every attempt and its error.
Which errors retry or fall back by default
When you omit onErrors, AnySolver uses a safe set of faults that another attempt can clear. It never includes user-input errors, so a misconfigured request is not retried into the same failure.
| Behavior | Default error codes |
|---|---|
| Retry | CAPTCHA_UNSOLVABLE, TASK_TIMEOUT, SERVICE_UNAVAILABLE, PROVIDER_UNREACHABLE, PROVIDER_RATE_LIMITED, PROXY_CONNECTION_FAILED, PROXY_BANNED |
| Fallback | All retry codes plus PROVIDER_GLOBAL_LIMIT_REACHED |
CAPTCHA_UNSOLVABLE means the provider failed a single solve, which usually succeeds on the next attempt, so it retries the same provider before falling back. Fallback's set adds PROVIDER_GLOBAL_LIMIT_REACHED, where the current provider is exhausted and only a different one can make progress. Override either list with onErrors to widen or narrow the trigger set. onErrors only accepts refundable provider-failure codes; any other code could never trigger another attempt, so it is rejected at validation instead of silently doing nothing.
Retry and fallback only apply to refundable failures, and you are billed once: the initial estimate is charged up front and adjusted to the final provider's price when the task finishes (charging the difference for a pricier provider, refunding it for a cheaper one, or refunding in full if every attempt fails refundably). A non-refundable failure is always terminal and keeps the charge, even if you list its code in onErrors.
Settings validation errors
Invalid settings fail fast with errorCode: "VALIDATION_ERROR" and a precise message. The most common:
| Message | Fix |
|---|---|
settings.routing.providerOrder can only be used when settings.routing.mode is "priority". | Drop providerOrder, or set mode to priority. |
settings.routing.provider cannot be used with settings.routing.mode "autoCheapest". A forced provider disables automatic provider selection. | Remove the auto* mode, or remove the forced provider. |
Priority routing requires a provider order. Pass settings.routing.providerOrder or configure a priority order on the API key. | Add providerOrder, or save a priority order on the key. |
Provider "CapSolver" cannot be in both settings.routing.providerOrder and settings.routing.excludedProviders. | Remove the provider from one of the two lists. |
settings.routing.excludedProviders excludes every provider that supports this task type. | Exclude fewer providers, or pick a broader task type. |
Unknown field settings.allowProxyReUse. Did you mean settings.allowProxyReuse? | Fix the field name. settings rejects unknown keys. |
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,
});
}