Compatibility API
The compatibility API speaks the anti-captcha compatible JSON protocol used by many captcha solving clients and
libraries. Point your client at https://api.noptcha.com, use a Noptcha key as clientKey, and keep your code.
Conventions
Section titled “Conventions”- Every request is
POSTwith a JSON body.Content-Typeis not required; the body is always parsed as JSON. - Every response is HTTP 200.
errorIdis0on success and1on error; thenerrorCodeanderrorDescriptionsay what went wrong (error codes). - The key is checked first: an invalid key is reported even when other fields are wrong.
- Task IDs are integers (53 bits, safe as JavaScript numbers). Times are Unix seconds.
- Two responses come from the infrastructure, not the protocol: HTTP 403 (a request that did not pass through our
edge network) and HTTP 500. Treat both as transient. Unknown paths, and known paths with another method
(
GET /createTask), are HTTP 404.
Endpoints
Section titled “Endpoints”| Endpoint | Body | Response |
|---|---|---|
POST /createTask |
clientKey, task; optional softId / appId |
taskId; for tasks solved at once also status: "ready", solution, cost |
POST /getTaskResult |
clientKey, taskId |
status: "processing", or "ready" with solution, cost, createTime, endTime, solveCount, ip |
POST /getBalance |
clientKey |
balance (US dollars, number) |
POST /reportIncorrectRecaptcha, /reportCorrectRecaptcha, /reportIncorrectImageCaptcha |
clientKey, taskId |
status: "success" |
Solving a task
Section titled “Solving a task”task.type is a compatibility name such as RecaptchaV2TaskProxyless; the other task fields are that type’s
parameters in anti-captcha’s field names (websiteURL, websiteKey, …). Each task type page
lists its names, fields and solution fields.
export NOPTCHA_KEY=nop_test_... # your test or live key
# 1. create the taskcurl -s -X POST "https://api.noptcha.com/createTask" \ -H 'Content-Type: application/json' \ -d @- <<EOF{ "clientKey": "$NOPTCHA_KEY", "task": { "type": "RecaptchaV2TaskProxyless", "websiteURL": "https://www.google.com/recaptcha/api2/demo", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" }}EOF
# 2. poll with the taskId from step 1 every few seconds until "status" is "ready"TASK_ID=...curl -s -X POST "https://api.noptcha.com/getTaskResult" \ -H 'Content-Type: application/json' \ -d @- <<EOF{ "clientKey": "$NOPTCHA_KEY", "taskId": $TASK_ID}EOFimport osimport time
import requests
API = "https://api.noptcha.com"KEY = os.environ["NOPTCHA_KEY"] # a nop_test_ key runs in the sandbox: free, fixed answers
def call(method, payload): r = requests.post(f"{API}/{method}", json={"clientKey": KEY, **payload}, timeout=30) r.raise_for_status() data = r.json() if data["errorId"]: raise RuntimeError(f"{data['errorCode']}: {data.get('errorDescription', '')}") return data
created = call("createTask", {"task": { "type": "RecaptchaV2TaskProxyless", "websiteURL": "https://www.google.com/recaptcha/api2/demo", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",}})result = created # some types are solved at once and come back with "status": "ready"deadline = time.time() + 300while result.get("status") != "ready": if time.time() > deadline: raise TimeoutError(created["taskId"]) time.sleep(3) result = call("getTaskResult", {"taskId": created["taskId"]})print(result["solution"])const API = 'https://api.noptcha.com';const KEY = process.env.NOPTCHA_KEY; // a nop_test_ key runs in the sandbox: free, fixed answers
async function call(method, payload) { const res = await fetch(`${API}/${method}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientKey: KEY, ...payload }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (data.errorId) throw new Error(`${data.errorCode}: ${data.errorDescription ?? ''}`); return data;}
const created = await call('createTask', { task: { type: "RecaptchaV2TaskProxyless", websiteURL: "https://www.google.com/recaptcha/api2/demo", websiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",} });let result = created; // some types are solved at once and come back with status "ready"const deadline = Date.now() + 300_000;while (result.status !== 'ready') { if (Date.now() > deadline) throw new Error(`timed out: ${created.taskId}`); await new Promise((r) => setTimeout(r, 3000)); result = await call('getTaskResult', { taskId: created.taskId });}console.log(result.solution);- Polling. Call
getTaskResultevery 1–3 seconds. Polling one task more than 5 times per second is answered withprocessingrather than an error, so a fast poller keeps working; it just does not get the answer sooner. - Results are kept for 10 minutes after the task completes; after that
getTaskResultreturnsERROR_NO_SUCH_CAPCHA_ID. - Failed tasks come back from
getTaskResultas errors (errorId: 1, for exampleERROR_CAPTCHA_UNSOLVABLEor a proxy error) and are not charged. - Tasks solved at once: some types can be answered while
createTaskis still open; the response then already hasstatus: "ready"and the solution. If such an immediate attempt fails,createTaskreturns the error and no task ID; nothing is charged. costingetTaskResultis a string with 6 decimals ("0.001000");balanceingetBalanceis a number.
Proxies
Section titled “Proxies”Names without Proxyless (for example RecaptchaV2Task) need a proxy: proxyType (http, https, socks4,
socks5), proxyAddress, proxyPort, and optionally proxyLogin and proxyPassword. Proxyless names ignore
proxy fields. See Proxies.
export NOPTCHA_KEY=nop_test_... # your test or live keycurl -s -X POST "https://api.noptcha.com/createTask" \ -H 'Content-Type: application/json' \ -d @- <<EOF{ "clientKey": "$NOPTCHA_KEY", "task": { "type": "RecaptchaV2Task", "websiteURL": "https://example.com/login", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", "proxyType": "http", "proxyAddress": "203.0.113.7", "proxyPort": 8080, "proxyLogin": "proxyuser", "proxyPassword": "proxypass" }}EOFimport os
import requests
API = "https://api.noptcha.com"KEY = os.environ["NOPTCHA_KEY"]
r = requests.post( f"{API}/createTask", json={ "clientKey": KEY, "task": { "type": "RecaptchaV2Task", "websiteURL": "https://example.com/login", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", "proxyType": "http", "proxyAddress": "203.0.113.7", "proxyPort": 8080, "proxyLogin": "proxyuser", "proxyPassword": "proxypass", }, }, timeout=30,)print(r.status_code, r.json())const API = 'https://api.noptcha.com';const KEY = process.env.NOPTCHA_KEY;
const res = await fetch(`${API}/createTask`, { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ clientKey: KEY, task: { type: "RecaptchaV2Task", websiteURL: "https://example.com/login", websiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", proxyType: "http", proxyAddress: "203.0.113.7", proxyPort: 8080, proxyLogin: "proxyuser", proxyPassword: "proxypass", }, }),});console.log(res.status, await res.json());Balance
Section titled “Balance”export NOPTCHA_KEY=nop_test_... # your test or live keycurl -s -X POST "https://api.noptcha.com/getBalance" \ -H 'Content-Type: application/json' \ -d @- <<EOF{ "clientKey": "$NOPTCHA_KEY"}EOFimport os
import requests
API = "https://api.noptcha.com"KEY = os.environ["NOPTCHA_KEY"]
r = requests.post( f"{API}/getBalance", json={ "clientKey": KEY, }, timeout=30,)print(r.status_code, r.json())const API = 'https://api.noptcha.com';const KEY = process.env.NOPTCHA_KEY;
const res = await fetch(`${API}/getBalance`, { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ clientKey: KEY, }),});console.log(res.status, await res.json());Needs the balance:read scope. balance is the amount available for new tasks: cash plus promotional credit (and any credit
limit), minus amounts held for tasks in progress. The native API shows each part (GET /v1/balance).
Reporting answers
Section titled “Reporting answers”After using a solution, you can report whether the site accepted it. Reports are accepted for solved tasks within 10 minutes after completion; only the first report per task is recorded. Reports are recorded as a quality signal; they do not refund the task.
export NOPTCHA_KEY=nop_test_... # your test or live keyTASK_ID=7259140378796032 # the task ID you got when creating the taskcurl -s -X POST "https://api.noptcha.com/reportIncorrectRecaptcha" \ -H 'Content-Type: application/json' \ -d @- <<EOF{ "clientKey": "$NOPTCHA_KEY", "taskId": $TASK_ID}EOFimport os
import requests
API = "https://api.noptcha.com"KEY = os.environ["NOPTCHA_KEY"]task_id = 7259140378796032 # the task ID you got when creating the task
r = requests.post( f"{API}/reportIncorrectRecaptcha", json={ "clientKey": KEY, "taskId": task_id, }, timeout=30,)print(r.status_code, r.json())const API = 'https://api.noptcha.com';const KEY = process.env.NOPTCHA_KEY;const taskId = 7259140378796032; // the task ID you got when creating the task
const res = await fetch(`${API}/reportIncorrectRecaptcha`, { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ clientKey: KEY, taskId: taskId, }),});console.log(res.status, await res.json());Callbacks
Section titled “Callbacks”callbackUrl in createTask makes Noptcha POST the final result to your server: the getTaskResult response plus
taskId. See Webhooks for signing, retries and the URL rules; an unacceptable URL is
ERROR_INCORRECT_SESSION_DATA and no task is created.
Not supported
Section titled “Not supported”- Idempotent retries: the protocol has no way to express them. A
createTaskthat timed out on the network may have created a task. If that matters, use the native API.
Differences you may notice
Section titled “Differences you may notice”- Keys are Noptcha keys (
nop_live_…,nop_test_…). - Rate limits are answered with codes your client already knows (
ERROR_NO_SLOT_AVAILABLEwhen creating too fast,processingwhen polling too fast), never with HTTP 429. See Rate limits. softIdandappIdare recorded with the task.