Errors & Status Codes
HTTP status is not the error signal
As mentioned in the Overview, every SolverCF response is HTTP 200 OK — captcha-solving and email endpoints alike — even when the request failed logically. Don't branch on the HTTP status code; check the response body instead, using the field described below for the endpoint family you're calling.
Captcha endpoints (token/extension/*, token/resultTaskPool)
These endpoints (createTask, getTaskResult, getBalance, resultTaskPool) report errors via errorId:
errorId |
Meaning |
|---|---|
0 |
Success |
1 |
Failed — check errorCode / errorDescription |
errorCode
errorCode |
Where | Meaning |
|---|---|---|
ERROR_KEY_DOES_NOT_EXIST |
getBalance |
clientKey doesn't match any account |
ERROR_CAPTCHA_UNSOLVABLE |
createTask, getTaskResult |
Task couldn't be created or resolved — see errorDescription for the specific reason (e.g. invalid/unsupported type, balance not enough, task not found) |
errorDescription always contains a human-readable explanation — treat errorCode as a coarse category and errorDescription as the detail. Programmatic branching (retry vs. give up) should key off errorDescription/context rather than errorCode alone, since one code covers several distinct underlying reasons.
Email endpoints (email/*)
The Temporary Email endpoints use a different, simpler error shape — there's no errorId/errorCode at all. Instead, status is either the literal string "success", or the error message itself:
// success
{ "status": "success", "messages": [] }
// failure — status IS the error message
{ "status": "balance not enough", "messages": [] }
Check status !== "success" to detect a failure on these endpoints, and treat the string as the human-readable message directly — there's no separate structured code to match against.
Task status values
Returned by Get Task Result and reflects where the task is in its lifecycle:
status |
Meaning |
|---|---|
created |
Task accepted, waiting for a worker to pick it up |
processing |
A worker is actively solving it |
ready |
Solved — solution is populated |
failed |
The worker couldn't solve it (site changed, invalid key, etc.) |
expired |
Sat unclaimed too long (over 1 minute) and was dropped — create a new task |
Keep polling while status is created or processing; stop on ready, failed, or expired. See Get Task Result → Polling strategy for recommended intervals and timeouts.
Troubleshooting by symptom
| Symptom | Likely cause | Fix |
|---|---|---|
errorId: 1, ERROR_KEY_DOES_NOT_EXIST |
Wrong or mistyped clientKey |
Copy the key fresh from the Dashboard |
errorId: 1, "balance not enough" |
Account balance below the task's price | Top up from Dashboard → Deposit |
errorId: 1, "Supported [...]" |
task.type doesn't match a known type |
Check spelling/casing against Create Task → Task types |
errorId: 1, "task not found" |
taskId doesn't belong to this clientKey, or was never created |
Double-check you're polling the taskId createTask actually returned |
getTaskResult stuck on processing past ~90s |
Worker hit an issue solving the target page | Treat as failed once you exceed your polling budget — see Task lifecycle & timeouts |
status: "expired" |
No worker claimed the task within 1 minute | Create a new task; if this happens repeatedly, check that websiteUrl/websiteKey are valid |
resultTaskPool fails with "pools is empty" |
No pre-solved token cached for that websiteKey yet |
Fall back to createTask with type: "TurnstileTaskPool" — see Result Task Pool |
| Token submitted but target site still rejects it | User agent mismatch | Submit the token together with the exact solution.userAgent returned by getTaskResult |
Still stuck? Reach out on Telegram: t.me/solvercf or the community group.