# 2captcha legacy API

> Use software written for the 2captcha in.php / res.php protocol with Noptcha - supported methods, responses, error codes and differences.

Source: https://noptcha.com/docs/2captcha-api/

For software written for the **2captcha** `in.php` / `res.php` protocol, Noptcha accepts the same requests on `https://api.noptcha.com/in.php` and `https://api.noptcha.com/res.php`. Use a Noptcha key as `key`. For new integrations prefer the [native API](https://noptcha.com/docs/native-api.md) or the [compatibility API](https://noptcha.com/docs/compatibility-api.md).

Tasks created here are ordinary tasks: same prices, limits and sandbox, and the task ID also works with `/getTaskResult` and `GET /v1/tasks/{id}`.

> **Keep the key out of the URL:** The protocol allows the key in the query string, and some clients send `res.php` requests as GET. URLs are often recorded by proxies, security tools and browsers. Send `POST` with a form body (or JSON) whenever your client allows it, and revoke a key in the console if it leaks.

## Requests

-   `GET` or `POST`. Parameters may be in the query string, a form body (`application/x-www-form-urlencoded` or `multipart/form-data` with the image in `file`), or a JSON body. A body parameter overrides the same one in the query.
-   Add `json=1` for JSON responses: `{"status": 1, "request": "…"}` on success, `{"status": 0, "request": "ERROR_…", "error_text": "…"}` on errors. Without it, responses are plain text: `OK|<value>`, `CAPCHA_NOT_READY` or an error code.
-   `header_acao=1` adds `Access-Control-Allow-Origin: *`; `OPTIONS` requests to both endpoints return 204 (CORS preflight).
-   Responses are HTTP 200, including errors and rate limits; read the body. One exception: when the service is overloaded, a `res.php` request that does not have `action` in the query string (for example a POST with `action` in the form body) gets HTTP 503 with `Retry-After: 1` and `ERROR_NO_SLOT_AVAILABLE`. Wait and retry.

## Create a task: `in.php`

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
curl -s "https://api.noptcha.com/in.php" \
  -d "key=$NOPTCHA_KEY" \
  -d 'method=userrecaptcha' \
  -d 'googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-' \
  -d 'pageurl=https://www.google.com/recaptcha/api2/demo' \
  -d 'json=1'
```


**Python**

```python
import os

import requests

API = "https://api.noptcha.com"
KEY = os.environ["NOPTCHA_KEY"]

# a form body keeps the key out of the URL
r = requests.post(f"{API}/in.php", data={
    "key": KEY,
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    "pageurl": "https://www.google.com/recaptcha/api2/demo",
    "json": "1",
}, timeout=30)
print(r.json())
```


**Node.js**

```js
const API = 'https://api.noptcha.com';
const KEY = process.env.NOPTCHA_KEY;

// a form body keeps the key out of the URL
const res = await fetch(`${API}/in.php`, {
  method: 'POST',
  body: new URLSearchParams({
    key: KEY,
    method: "userrecaptcha",
    googlekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl: "https://www.google.com/recaptcha/api2/demo",
    json: "1",
  }),
});
console.log(await res.json());
```

The response is the task ID: `OK|7259140378796032`, or `{"status": 1, "request": "7259140378796032"}` with `json=1`. Only errors that can be detected before the task is created (key, balance, rate limits, parameter format) are returned here; the outcome of the task, including failures, is always reported by `res.php`.

| `method` | Task type | Parameters |
| --- | --- | --- |
| `userrecaptcha` | [reCAPTCHA v2](https://noptcha.com/docs/task-types/recaptcha-v2.md); `enterprise=1`: [v2 Enterprise](https://noptcha.com/docs/task-types/recaptcha-v2-enterprise.md); `version=v3`: [v3](https://noptcha.com/docs/task-types/recaptcha-v3.md) (with `enterprise=1`: [v3 Enterprise](https://noptcha.com/docs/task-types/recaptcha-v3-enterprise.md)) | `googlekey`, `pageurl`, `invisible=1`, `action`, `min_score` (v3), `data-s` (Enterprise), `domain` (`recaptcha.net` or `google.com`), `cookies` (`K1:V1;K2:V2`), `userAgent` |
| `hcaptcha` | [hCaptcha](https://noptcha.com/docs/task-types/hcaptcha.md) | `sitekey`, `pageurl`, `invisible`, `data` (rqdata), `userAgent` |
| `turnstile` | [Cloudflare Turnstile](https://noptcha.com/docs/task-types/turnstile.md) | `sitekey`, `pageurl`, `action`, `data` (cData), `pagedata`, `userAgent` |
| `funcaptcha` | [FunCaptcha](https://noptcha.com/docs/task-types/funcaptcha.md) | `publickey`, `pageurl`, `surl`, `data[key]=value`, `userAgent` |
| `base64` (`body`) or `post` (`file`) | [Image to text](https://noptcha.com/docs/task-types/image-to-text.md) | `phrase`, `regsense`, `numeric` (1 or 2), `calc`, `min_len`, `max_len` |
| same, with `recaptcha=1` | [reCAPTCHA image grid](https://noptcha.com/docs/task-types/image-grid-recaptcha.md) | `textinstructions` (required), `recaptcharows` = `recaptchacols` (1, 3 or 4) |
| same, with `recaptcha=1&img_type=funcaptcha` | [FunCaptcha image](https://noptcha.com/docs/task-types/image-funcaptcha.md) | `textinstructions` |

Proxies: `proxy=login:password@host:port` (or `host:port`) and `proxytype=HTTP|HTTPS|SOCKS4|SOCKS5`. Types that do not use a proxy (reCAPTCHA v3, images) ignore it.

Other methods (for example `geetest`, `amazon_waf`, `keycaptcha`, `capy`, `canvas`, `coordinatescaptcha`, `textcaptcha`, `img_type=hcaptcha`) and `pingback` are answered with `ERROR_BAD_PARAMETERS`. Parameters such as `language`, `lang` and `imginstructions` are ignored.

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
curl -s "https://api.noptcha.com/in.php" \
  -d "key=$NOPTCHA_KEY" \
  -d 'method=turnstile' \
  -d 'sitekey=1x00000000000000000000AA' \
  -d 'pageurl=https://example.com/signup' \
  -d 'json=1'
```


**Python**

```python
import os

import requests

API = "https://api.noptcha.com"
KEY = os.environ["NOPTCHA_KEY"]

# a form body keeps the key out of the URL
r = requests.post(f"{API}/in.php", data={
    "key": KEY,
    "method": "turnstile",
    "sitekey": "1x00000000000000000000AA",
    "pageurl": "https://example.com/signup",
    "json": "1",
}, timeout=30)
print(r.json())
```


**Node.js**

```js
const API = 'https://api.noptcha.com';
const KEY = process.env.NOPTCHA_KEY;

// a form body keeps the key out of the URL
const res = await fetch(`${API}/in.php`, {
  method: 'POST',
  body: new URLSearchParams({
    key: KEY,
    method: "turnstile",
    sitekey: "1x00000000000000000000AA",
    pageurl: "https://example.com/signup",
    json: "1",
  }),
});
console.log(await res.json());
```

## Get the result: `res.php?action=get`

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
TASK_ID=7259140378796032  # the ID you got from in.php
curl -s "https://api.noptcha.com/res.php" \
  -d "key=$NOPTCHA_KEY" \
  -d 'action=get' \
  -d "id=$TASK_ID" \
  -d 'json=1'
```


**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

# a form body keeps the key out of the URL
r = requests.post(f"{API}/res.php", data={
    "key": KEY,
    "action": "get",
    "id": str(task_id),
    "json": "1",
}, timeout=30)
print(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

// a form body keeps the key out of the URL
const res = await fetch(`${API}/res.php`, {
  method: 'POST',
  body: new URLSearchParams({
    key: KEY,
    action: "get",
    id: String(taskId),
    json: "1",
  }),
});
console.log(await res.json());
```

| Response | Meaning |
| --- | --- |
| `CAPCHA_NOT_READY` | Not finished yet. Poll again in a few seconds. |
| `OK|<answer>` | Solved. Tokens are returned as is (with `json=1` also `useragent`, and `respKey` for hCaptcha); image text as text; image grids as `click:3/8/9` (counted from 1, or `No_matching_images`); other answers as JSON. |
| `ERROR_CAPTCHA_UNSOLVABLE`, `ERROR_PROXY_CONNECTION_FAILED`, `ERROR_TOKEN_EXPIRED` | The task failed (`error_text` says why). Not charged. |

-   `action=get2` adds the price: `OK|<answer>|<price>` (with `json=1`, a `price` field), 6 decimal places in US dollars.
-   `action=get&ids=1,2,3` reads up to 20 tasks at once; answers are joined with `|` in the same order, with `CAPCHA_NOT_READY` or an error code in the place of tasks that are not solved.

## Other actions

| `action` | Response |
| --- | --- |
| `getbalance` | Available balance in US dollars, for example `12.345678` |
| `reportbad`, `reportgood` (`id`) | `OK_REPORT_RECORDED`. Reports are recorded as a quality signal; they do not refund. |
| anything else | `ERROR_EMPTY_ACTION` |

**curl**

```bash
export NOPTCHA_KEY=nop_test_...  # your test or live key
curl -s "https://api.noptcha.com/res.php" \
  -d "key=$NOPTCHA_KEY" \
  -d 'action=getbalance' \
  -d 'json=1'
```


**Python**

```python
import os

import requests

API = "https://api.noptcha.com"
KEY = os.environ["NOPTCHA_KEY"]

# a form body keeps the key out of the URL
r = requests.post(f"{API}/res.php", data={
    "key": KEY,
    "action": "getbalance",
    "json": "1",
}, timeout=30)
print(r.json())
```


**Node.js**

```js
const API = 'https://api.noptcha.com';
const KEY = process.env.NOPTCHA_KEY;

// a form body keeps the key out of the URL
const res = await fetch(`${API}/res.php`, {
  method: 'POST',
  body: new URLSearchParams({
    key: KEY,
    action: "getbalance",
    json: "1",
  }),
});
console.log(await res.json());
```

## Errors

| Code | When |
| --- | --- |
| `ERROR_WRONG_USER_KEY` | the key is not in Noptcha’s format |
| `ERROR_KEY_DOES_NOT_EXIST` | the key is unknown, revoked or expired, lacks the scope, or the organization is suspended (`error_text` says which) |
| `ERROR_ZERO_BALANCE` | balance too low, or the key’s budget is used up |
| `ERROR_IP_NOT_ALLOWED` | the client IP is not in the key’s allowed ranges |
| `IP_BANNED` | too many invalid keys from this IP ([details](https://noptcha.com/docs/authentication.md#invalid-keys-and-ip-blocking)) |
| `ERROR_NO_SLOT_AVAILABLE` | creating tasks too fast, concurrency limit, or no capacity; wait a few seconds and resend |
| `ERROR_BAD_PARAMETERS`, `ERROR_WRONG_GOOGLEKEY`, `ERROR_GOOGLEKEY`, `ERROR_PAGEURL`, `ERROR_PROXY_FORMAT` | missing or invalid parameters, unsupported method, type not allowed for the key, `pingback` |
| `ERROR_ZERO_CAPTCHA_FILESIZE`, `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | the image is empty, or too large (bytes, or over the [pixel limit](https://noptcha.com/docs/limits.md#images)) |
| `ERROR_UPLOAD` | the image is not valid base64 ([accepted forms](https://noptcha.com/docs/limits.md#images)) |
| `ERROR_IMAGE_TYPE_NOT_SUPPORTED` | the image is not a PNG, JPEG, GIF, WebP or BMP file, or is malformed or truncated |
| `ERROR_WRONG_ID_FORMAT`, `ERROR_WRONG_CAPTCHA_ID` | the `id` is not a number, or the task is unknown, expired or not yours |
| `ERROR_REPORT_NOT_RECORDED` | report for a task that is not solved |

Polling one task too often, or reading a batch beyond the rate limit, is answered with `CAPCHA_NOT_READY` rather than an error, so clients keep polling.

## Differences from 2captcha

-   Keys are Noptcha keys (`nop_live_…`, `nop_test_…`), and only HTTPS is accepted.
-   No `ERROR: 1001`–`1005` account bans; [rate limits](https://noptcha.com/docs/limits.md) apply instead, with the codes above.
-   A second report for the same task also returns `OK_REPORT_RECORDED`; reports never refund.
-   Prices (`get2`) and the balance have 6 decimal places.
-   Not available: `pingback` (use `callbackUrl` or `callback_url` with the [other APIs](https://noptcha.com/docs/webhooks.md)), `debug_dump`, developer parameters other than `soft_id`, `language`, `lang`, `imginstructions`.
-   `ids` batches are limited to 20 tasks.
-   `OPTIONS` requests get a CORS preflight response.
-   Test keys work as in the [sandbox](https://noptcha.com/docs/sandbox.md): the magic values go in `googlekey`, `sitekey`, `publickey` or `textinstructions`, and outcomes are reported by `res.php` as for live keys.
