# Quickstart

> Solve a first captcha task with a free Noptcha test key, using the compatibility API or the native v1 API, in curl, Python or Node.js.

Source: https://noptcha.com/docs/quickstart/

This page solves a reCAPTCHA v2 task with a **test key**. Test keys (`nop_test_…`) run in the [sandbox](https://noptcha.com/docs/sandbox.md): nothing is charged, no real solver is called, and every example on this site works with them unchanged. Switch to a live key (`nop_live_…`) when you are ready.

1.  **Create a test key** in the console: **API keys → Create key**, mode **Test**. The key is shown only once; store it as an environment variable:
    
    ```sh
    export NOPTCHA_KEY=nop_test_...
    ```
    
2.  **Pick an API.** If your software already speaks the anti-captcha protocol, use the compatibility API; otherwise the native API (v1) is simpler: one request can wait for the result.
    
3.  **Run one of the examples below.** The Python examples need `pip install requests`. The Node.js examples need Node.js 22 or later (they use the built-in `fetch` and top-level `await`); save them as `.mjs` files, for example `node solve.mjs`.
    

## Native API (v1)

`POST /v1/tasks` with `Prefer: wait=60` waits up to 60 seconds and returns the finished task (HTTP 200). If the task is not finished by then you get HTTP 202 and poll `GET /v1/tasks/{id}`. The `Idempotency-Key` header makes a retry after a network error return the same task instead of creating a second one.

**curl**

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

# waits up to 60 s; 200 = finished (solved or failed), 202 = still processing
IDEMPOTENCY_KEY=$(uuidgen)  # one key per task you want; a retry of the same request reuses it
curl -s -X POST "https://api.noptcha.com/v1/tasks" \
  -H "Authorization: Bearer $NOPTCHA_KEY" \
  -H 'Prefer: wait=60' \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H 'Content-Type: application/json' \
  -d @- <<EOF
{
  "type": "recaptcha.v2",
  "params": {
    "website_url": "https://www.google.com/recaptcha/api2/demo",
    "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
  }
}
EOF

# on 202: long-poll with the "id" from the response
TASK_ID=...
curl -s "https://api.noptcha.com/v1/tasks/$TASK_ID?wait=60" \
  -H "Authorization: Bearer $NOPTCHA_KEY"
```


**Python**

```python
import os
import time
import uuid

import requests

API = "https://api.noptcha.com"
HEADERS = {"Authorization": f"Bearer {os.environ['NOPTCHA_KEY']}"}  # a nop_test_ key runs in the sandbox

# One Idempotency-Key per task you want. If you retry this request after a network error, send the same key and
# the same body bytes: you get the same task back, not a second one (see Idempotency on the Tasks page).
idempotency_key = str(uuid.uuid4())
r = requests.post(
    f"{API}/v1/tasks",
    headers={**HEADERS, "Prefer": "wait=60", "Idempotency-Key": idempotency_key},
    json={
        "type": "recaptcha.v2",
        "params": {
            "website_url": "https://www.google.com/recaptcha/api2/demo",
            "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
        },
    },
    timeout=100,
)
task = r.json()
if r.status_code >= 400:
    raise RuntimeError(f"{task['code']}: {task.get('detail', '')}")
while task["status"] == "processing":  # 202: not finished within the wait
    time.sleep(1)  # a long poll can return early (for example when wait slots are full)
    r = requests.get(f"{API}/v1/tasks/{task['id']}", params={"wait": 60}, headers=HEADERS, timeout=100)
    task = r.json()
    if r.status_code >= 400:
        raise RuntimeError(f"{task['code']}: {task.get('detail', '')}")

if task["status"] == "solved":
    print(task["solution"])
else:
    print("failed:", task["error"]["code"], task["error"]["message"])
```


**Node.js**

```js
import { randomUUID } from 'node:crypto';

const API = 'https://api.noptcha.com';
const HEADERS = { Authorization: `Bearer ${process.env.NOPTCHA_KEY}` }; // a nop_test_ key runs in the sandbox

async function v1(path, init = {}) {
  const res = await fetch(`${API}${path}`, { ...init, headers: { ...HEADERS, ...init.headers } });
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.code}: ${data.detail ?? ''}`);
  return data;
}

// One Idempotency-Key per task you want. If you retry this request after a network error, send the same key and
// the same body bytes: you get the same task back, not a second one (see Idempotency on the Tasks page).
const idempotencyKey = randomUUID();
let task = await v1('/v1/tasks', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Prefer: 'wait=60', 'Idempotency-Key': idempotencyKey },
  body: JSON.stringify({
    type: "recaptcha.v2",
    params: {
      website_url: "https://www.google.com/recaptcha/api2/demo",
      website_key: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    },
  }),
});
while (task.status === 'processing') {
  await new Promise((r) => setTimeout(r, 1000)); // a long poll can return early (for example when wait slots are full)
  task = await v1(`/v1/tasks/${task.id}?wait=60`); // long poll
}
if (task.status === 'solved') console.log(task.solution);
else console.log('failed:', task.error.code, task.error.message);
```

A solved task looks like this (in the sandbox the token is `NOPTCHA_TEST_TOKEN_<task id>`):

```json
{
  "id": "7259140378796032",
  "type": "recaptcha.v2",
  "status": "solved",
  "solution": { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" },
  "error": null,
  "cost": { "amount": "0.000000", "currency": "USD" },
  "sandbox": true
}
```

Fields left out here: `params`, `metadata`, `created_at`, `completed_at`. See the [task object](https://noptcha.com/docs/native-api.md#the-task-object).

## Compatibility API

`POST /createTask` returns a `taskId`; then call `POST /getTaskResult` every 1–3 seconds until `status` is `ready`.

**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);
```

```json
{
  "errorId": 0,
  "status": "ready",
  "solution": { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" },
  "cost": "0.000000"
}
```

`getTaskResult` also returns `createTime`, `endTime`, `solveCount` and `ip`.

## Try a failure

The sandbox simulates outcomes from magic values: set `website_key` (compatibility API: `websiteKey`) to `test-unsolvable`, `test-timeout`, `test-proxy-error` or `test-slow-5` and run the same code. See [Sandbox](https://noptcha.com/docs/sandbox.md).

## With an SDK

The [Python and Node.js SDKs](https://noptcha.com/docs/sdks.md) wrap the native API: one call creates the task and waits for the solution, with retries, idempotency keys and typed errors built in.

> **Next:** Find the parameters of your captcha on its [task type page](https://noptcha.com/docs/task-types.md), then read [Errors](https://noptcha.com/docs/errors.md) and [Rate limits](https://noptcha.com/docs/limits.md) before going live.
