# Compatibility API

> The anti-captcha compatible JSON protocol of the Noptcha API - createTask, getTaskResult, getBalance and reports - and how it differs.

Source: https://noptcha.com/docs/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

-   Every request is `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON.
-   Every response is **HTTP 200**. `errorId` is `0` on success and `1` on error; then `errorCode` and `errorDescription` say what went wrong ([error codes](https://noptcha.com/docs/errors.md#compatibility-api)).
-   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

| Endpoint | Body | Response |
| --- | --- | --- |
| [`POST /createTask`](https://noptcha.com/docs/api-reference/compat-create-task.md) | `clientKey`, `task`; optional `softId` / `appId` | `taskId`; for tasks solved at once also `status: "ready"`, `solution`, `cost` |
| [`POST /getTaskResult`](https://noptcha.com/docs/api-reference/compat-get-task-result.md) | `clientKey`, `taskId` | `status: "processing"`, or `"ready"` with `solution`, `cost`, `createTime`, `endTime`, `solveCount`, `ip` |
| [`POST /getBalance`](https://noptcha.com/docs/api-reference/compat-get-balance.md) | `clientKey` | `balance` (US dollars, number) |
| [`POST /reportIncorrectRecaptcha`](https://noptcha.com/docs/api-reference/compat-report-incorrect-recaptcha.md), [`/reportCorrectRecaptcha`](https://noptcha.com/docs/api-reference/compat-report-correct-recaptcha.md), [`/reportIncorrectImageCaptcha`](https://noptcha.com/docs/api-reference/compat-report-incorrect-image-captcha.md) | `clientKey`, `taskId` | `status: "success"` |

## 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](https://noptcha.com/docs/task-types.md) lists its names, fields and solution fields.

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key

# 1. create the task
curl -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
}
EOF
```


**Python**

```python
import os
import 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() + 300
while result.get("status") != "ready":
    if time.time() > deadline:
        raise TimeoutError(created["taskId"])
    time.sleep(3)
    result = call("getTaskResult", {"taskId": created["taskId"]})
print(result["solution"])
```


**Node.js**

```js
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 `getTaskResult` every 1–3 seconds. Polling one task more than 5 times per second is answered with `processing` rather 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 `getTaskResult` returns `ERROR_NO_SUCH_CAPCHA_ID`.
-   **Failed tasks** come back from `getTaskResult` as errors (`errorId: 1`, for example `ERROR_CAPTCHA_UNSOLVABLE` or a proxy error) and are not charged.
-   **Tasks solved at once**: some types can be answered while `createTask` is still open; the response then already has `status: "ready"` and the solution. If such an immediate attempt fails, `createTask` returns the error and no task ID; nothing is charged.
-   `cost` in `getTaskResult` is a string with 6 decimals (`"0.001000"`); `balance` in `getBalance` is a number.

### 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](https://noptcha.com/docs/proxies.md).

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
curl -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"
  }
}
EOF
```


**Python**

```python
import 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())
```


**Node.js**

```js
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

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
curl -s -X POST "https://api.noptcha.com/getBalance" \
  -H 'Content-Type: application/json' \
  -d @- <<EOF
{
  "clientKey": "$NOPTCHA_KEY"
}
EOF
```


**Python**

```python
import 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())
```


**Node.js**

```js
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

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.

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
TASK_ID=7259140378796032  # the task ID you got when creating the task
curl -s -X POST "https://api.noptcha.com/reportIncorrectRecaptcha" \
  -H 'Content-Type: application/json' \
  -d @- <<EOF
{
  "clientKey": "$NOPTCHA_KEY",
  "taskId": $TASK_ID
}
EOF
```


**Python**

```python
import 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())
```


**Node.js**

```js
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

`callbackUrl` in `createTask` makes Noptcha POST the final result to your server: the `getTaskResult` response plus `taskId`. See [Webhooks](https://noptcha.com/docs/webhooks.md) for signing, retries and the URL rules; an unacceptable URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created.

## Not supported

-   Idempotent retries: the protocol has no way to express them. A `createTask` that timed out on the network may have created a task. If that matters, use the [native API](https://noptcha.com/docs/native-api/tasks.md#idempotency).

## 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_AVAILABLE` when creating too fast, `processing` when polling too fast), never with HTTP 429. See [Rate limits](https://noptcha.com/docs/limits.md).
-   `softId` and `appId` are recorded with the task.
