Reference
API reference
One envelope for every challenge type. The only part that varies is the contents of the task object — and whether you wait on the call or collect it later.
Tip
task.type and its fields, nothing more. Post it to /v1/getResult and wait, or to /v1/createTask and pick it up from /v1/checkTask.Endpoint#
The call is synchronous: it holds open until the challenge is solved or fails, then returns the finished result. Nothing to poll, no job id to store — for most integrations this is the whole API.
Note
Request#
| Property | Type | Required | Description |
|---|---|---|---|
clientKey | string | Required | Your API key. May also be sent as an Authorization: Bearer header instead of in the body. |
task | object | Required | The challenge to solve. Its fields depend on task.type — see the switcher below. |
task.type | string | Required | Selects which challenge is being solved and therefore which other fields apply. Case-insensitive. |
softId | string | Optional | Optional label echoed back in the response. Useful for correlating your own logs. |
The task object#
task.type decides which fields the rest of the object takes. Pick a type below and both the field table and the code sample update — the URL fragment changes too, so you can link someone straight to the variant they need.
reCAPTCHA v3 — Score-based, invisible. Returns a token carrying a passing score for your action.
median —proxy requiredreturns solution.tokenfull page →
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Required | Literal "ReCaptchaV3". Also accepts: ReCaptchaV3Enterprise, ReCaptchaV3Fast, ReCaptchaV3FastEnterprise, ReCaptchaV3Max, ReCaptchaV3EnterpriseMax. Matching is case-insensitive. |
url | string | Required | The page the challenge is served on.e.g. https://example.com/login |
site_key | string | Required | The site key from the reCAPTCHA script tag.e.g. 6LdyC2cUAAAAACGuDKpXeDorzUDWXmdqeg-xy696 |
action | string | Optional | The action name the page passes to grecaptcha.execute. Must match exactly or verification fails.e.g. login |
proxy | string | Required | Upstream proxy. Accepts host:port, host:port:user:pass, or a full http:// URL. Use a sticky session — a rotating exit IP mid-solve looks like a hijacked session and will fail.e.g. proxy.example.com:9090:user:pass |
user_agent | string | Optional | Pin the User-Agent used for the solve. Omit it and a coherent one is chosen for you and returned in the response — match whatever you send downstream.e.g. Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 … |
# pip install voidcaptcha
from voidcaptcha import VoidCaptcha
client = VoidCaptcha("void_a1b2c3d4e5f60718293a4b5c6d7e8f90")
result = client.recaptcha_v3(
url="https://example.com/login",
site_key="6LdyC2cUAAAAACGuDKpXeDorzUDWXmdqeg-xy696",
action="login",
proxy="proxy.example.com:9090:user:pass",
)
print(result.value){
"errorId": 0,
"status": "ready",
"solution": {
"token": "03AGdBq25V6…",
"userAgent": "Mozilla/5.0 …"
},
"cost": "$0.0008",
"balance": "$411.24",
"elapsedMs": 4820
}Response#
| Property | Type | Required | Description |
|---|---|---|---|
errorId | number | Required | Zero on success, non-zero on failure. Check this first. |
status | string | Required | ready on success, failed otherwise. |
solution | object | Optional | Present on success. The field carrying the result varies by type. |
solution.userAgent | string | Optional | The User-Agent the solve ran under. Send this downstream — cookie-based types are bound to it. |
solution.elapsed_time | string | Optional | Seconds the solve took, as a string. |
cost | string | Optional | What this solve charged. "$0.0000" for test keys and refunded failures. |
balance | string | Optional | Your remaining balance after the call. |
errorCode | string | Optional | Present on failure. A stable string such as ERROR_IP_BLOCKED. |
errorDescription | string | Optional | Present on failure. A human-readable explanation, safe to log. |
Check errorId rather than the HTTP status. A solve that fails for a reason on the target’s side — a blocked IP, an unsolvable challenge, a timeout — answers 200 with a non-zero errorId: it is a well-formed response describing a failure, not a transport error, and nothing is charged for it. A status outside 2xx means the call did not go through at all — a bad key, a malformed request, an empty balance or a thread limit.
The asynchronous pair#
Some fleets cannot hold a connection open for the length of a solve — a serverless function with a short ceiling, or a worker that must not block on one task. Those hand the task over and collect it later. Same envelope, same task object, same error codes; only the shape of the wait changes.
The body is exactly what you would have posted to /v1/getResult. The response returns as soon as the task is accepted, not when it is finished.
| Property | Type | Required | Description |
|---|---|---|---|
errorId | number | Required | Zero once the task is accepted. A rejected task fails here rather than later. |
status | string | Required | processing once the task is accepted and running. |
taskId | string | Optional | Present on success. The handle you pass to checkTask. |
| Property | Type | Required | Description |
|---|---|---|---|
clientKey | string | Required | The same key the task was created with. A task is only visible to its own key. |
taskId | string | Required | The handle createTask returned. |
/v1/checkTask answers with status: "processing" while the solve is still running, and with the same finished envelope the synchronous call returns — solution, cost, balance — once it is not. Poll no faster than every two seconds; a tighter loop earns ERROR_RATE_LIMITED and does not make the solve arrive sooner.
// POST /v1/createTask
{ "clientKey": "void_a1b2c3d4e5f60718293a4b5c6d7e8f90", "task": { "type": "ReCaptchaV3", "...": "..." } }
→ { "errorId": 0, "status": "processing", "taskId": "vt_8f3c1d92a4b7" }
// POST /v1/checkTask
{ "clientKey": "void_a1b2c3d4e5f60718293a4b5c6d7e8f90", "taskId": "vt_8f3c1d92a4b7" }
→ { "errorId": 0, "status": "processing" }
→ { "errorId": 0, "status": "ready", "solution": { "token": "03AGdBq25V6…" } }Warning
/v1/checkTask answers ERROR_TASK_NOT_FOUND and no amount of further polling will produce it. Collect a result in the session that created it, and treat a lost one as a solve to repeat rather than one to recover.Note
/v1/checkTask is deliberately not called getTaskResult. Next to a synchronous /v1/getResult, a polling endpoint one word away from it is a name people misread under pressure. A task is billed once, on the solve, no matter how many times you check it. Clients written for Anti-Captcha or CapSolver, which do call it getTaskResult, use the separate compatibility endpoints.Rate limits#
Accounts are limited by concurrent solves in flight rather than by requests per second, which suits a synchronous API better. The default is ten; exceeding it returns ERROR_THREAD_LIMIT with HTTP 429. Ask support to raise it and mention the throughput you need.
Idempotency#
Solves are not idempotent — each call performs real work against the target and is charged on success. Do not blind-retry a request that timed out client-side without checking whether it actually completed; use softId to correlate.
Errors#
Failures carry a numeric errorId, a stable errorCode string and a description. The error reference lists every code together with whether retrying is worth attempting.
Last updated on 2 August 2026