# Noptcha documentation (full text) > Noptcha is a captcha solving API at https://api.noptcha.com. It has two APIs sharing keys, prices and tasks: an anti-captcha compatible JSON protocol (POST /createTask, then poll POST /getTaskResult; key in the clientKey body field; every response is HTTP 200 with errorId/errorCode) and a native REST API under /v1/ (POST /v1/tasks with "Prefer: wait=N" to wait up to 90 s, Idempotency-Key for safe retries, Authorization: Bearer , RFC 9457 errors). A 2captcha-style in.php/res.php endpoint exists for legacy clients. Keys start with nop_live_ (charged) or nop_test_ (free sandbox with fixed answers). Only solved tasks are charged. --- # Introduction > What the Noptcha API does, its two APIs, and where to start. Source: https://noptcha.com/docs/ Noptcha is a captcha solving API. You send a **task** (the captcha type and the parameters of the page that shows it), Noptcha solves it, and you get a **solution** (a token, cookies, text or the selected images). You pay only for solved tasks. ## Two APIs, one account Both APIs live on `https://api.noptcha.com` and share keys, prices, balance, task state and limits. A task created through one can be read through the other. | | [Compatibility API](https://noptcha.com/docs/compatibility-api.md) | [Native API (v1)](https://noptcha.com/docs/native-api.md) | | --- | --- | --- | | Protocol | anti-captcha compatible JSON: `POST /createTask`, `/getTaskResult`, `/getBalance` | REST + JSON under `/v1/` | | Key | `clientKey` in the body | `Authorization: Bearer ` | | Errors | always HTTP 200; `errorId` and `errorCode` | HTTP status + RFC 9457 problem details | | Waiting | poll `getTaskResult` | `Prefer: wait=N` waits up to 90 seconds in the same request, or poll | | Retries | — | `Idempotency-Key`: a retried request never creates a second task | | Task IDs | integers | strings (the same number) | | Use it when | you already have software for the anti-captcha protocol | you write a new integration | There is also a [2captcha legacy API](https://noptcha.com/docs/2captcha-api.md) (`/in.php`, `/res.php`) for software written for that protocol. ## How a task works 1. Create a task with a type and its parameters ([task types](https://noptcha.com/docs/task-types.md)). 2. Wait for the result: poll, or ask the v1 API to wait for you. 3. Use the solution on the target site. Report whether it worked, if you like: reports are recorded as a quality signal (they do not refund). Results are kept for 10 minutes after a task completes. ## Next steps - [Quickstart](https://noptcha.com/docs/quickstart.md): solve a first task with a free test key. - [Keys and authentication](https://noptcha.com/docs/authentication.md) and the [sandbox](https://noptcha.com/docs/sandbox.md). - [Errors](https://noptcha.com/docs/errors.md), [rate limits](https://noptcha.com/docs/limits.md), [versioning](https://noptcha.com/docs/versioning.md). - [API reference](https://noptcha.com/docs/api-reference.md), generated from the [OpenAPI description](https://noptcha.com/openapi.json). - For AI agents and tools: [llms.txt](https://noptcha.com/llms.txt) and [llms-full.txt](https://noptcha.com/llms-full.txt) contain this documentation as plain text. --- # 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 @- <= 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_`): ```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 @- < 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. --- # SDKs > The official Noptcha client libraries for Python and Node.js / TypeScript - solve a captcha in one call, typed errors, webhook verification. Source: https://noptcha.com/docs/sdks/ Official client libraries for the [native API (v1)](https://noptcha.com/docs/native-api.md). Both are small layers over the HTTP API with **no runtime dependencies**, and both work with test keys, so every example on this page runs in the [sandbox](https://noptcha.com/docs/sandbox.md) unchanged. | | Python | Node.js / TypeScript | | --- | --- | --- | | Install | `pip install noptcha` | `npm install noptcha` | | Requires | Python 3.11 or later | Node.js 22 or later; ES modules, standard web APIs only (`fetch`, Web Crypto) | | Clients | `Noptcha` (blocking), `AsyncNoptcha` (asyncio) | `Noptcha` (promises) | | Types | `TypedDict`s for every parameter, solution and response | TypeScript types; parameters and solutions typed by task type | Both read the key from the `NOPTCHA_KEY` environment variable unless you pass one (surrounding whitespace, such as the newline at the end of a key file, is removed; a key with other invalid characters is rejected before any request). Keep the key on your server; never ship it to a browser. The API address must be `https://` (plain `http://` is only accepted for `localhost`, `127.0.0.1` and `::1`). ## Solve a captcha `solve` creates the task with `Prefer: wait=60`, so most tasks come back solved from a single request; otherwise it long-polls `GET /v1/tasks/{id}` until the task is final, and returns the solved task or raises / rejects with `TaskFailedError`. **Python** ```python from noptcha import Noptcha, TaskFailedError client = Noptcha() # reads NOPTCHA_KEY; a nop_test_ key runs in the sandbox try: task = client.solve( "recaptcha.v2", { "website_url": "https://www.google.com/recaptcha/api2/demo", "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", }, ) print(task["solution"]) except TaskFailedError as e: # unsolvable, timeout, proxy_error... (not charged) print("failed:", e.code) ``` **Node.js** ```js import { Noptcha, TaskFailedError } from 'noptcha'; const client = new Noptcha(); // reads NOPTCHA_KEY; a nop_test_ key runs in the sandbox try { const task = await client.solve('recaptcha.v2', { website_url: "https://www.google.com/recaptcha/api2/demo", website_key: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", }); console.log(task.solution); } catch (err) { if (!(err instanceof TaskFailedError)) throw err; console.log('failed:', err.code); // unsolvable, timeout, proxy_error... (not charged) } ``` What `solve` does for you: - **Idempotency.** Every create request carries an `Idempotency-Key` (a new UUID unless you pass your own), and a retry sends the same bytes, so a retry after a network error returns the same task instead of a second one. If the retry arrives while the first attempt is still being processed (`409 idempotency_conflict`), the client waits for it with back-off. An error thrown while creating carries the key that was sent (`e.idempotency_key` / `e.idempotencyKey`): call again with `idempotency_key=` / `idempotencyKey:` set to it and the same arguments to get the same task back. To be able to retry after your process crashes too, generate the key yourself and store it before the call. - **Retries.** Connection errors, `429` and `5xx` responses (including a proxy’s `502` or `504`) are retried (2 times by default), waiting `Retry-After` when the response has one. - **Waiting.** Long polls of 60 seconds (`poll_wait` / `pollWait`); when the server returns early because too many requests are waiting, the client pauses before asking again, as [Rate limits](https://noptcha.com/docs/limits.md) asks. - **Timeout and cancellation.** `solve` gives up after 5 minutes by default (`timeout` in seconds in Python, `timeoutMs` in Node.js), including the create request; once the task exists it throws `WaitTimeoutError` with the task ID, and the task itself goes on. Cancel with a `threading.Event` (`cancel=`) or an `AbortSignal` (`signal`): `solve` then throws `OperationCancelledError` with the task ID (or, if the task was not created yet, the idempotency key). With `AsyncNoptcha`, cancel the coroutine as usual. - **asyncio.** `AsyncNoptcha` runs its requests in its own pool of threads (`max_concurrency`, 64 by default), not in the event loop’s default executor, so long polls never hold up the rest of your application. Close it when you are done (`async with AsyncNoptcha() as client:` or `client.close()`). The parameters of each captcha are on its [task type page](https://noptcha.com/docs/task-types.md). In TypeScript they are checked against the task type, and `task.solution` has the type’s solution type. ## Step by step The steps `solve` combines are available on their own: create a task, wait for it (this returns a failed task instead of throwing), and [report](https://noptcha.com/docs/native-api/tasks.md) whether the answer worked. **Python** ```python from noptcha import Noptcha client = Noptcha() params = { "website_url": "https://www.google.com/recaptcha/api2/demo", "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", } task = client.create_task("recaptcha.v2", params, metadata={"order": "A-17"}) print(task["id"], task["status"]) # usually "processing" task = client.wait_for_task(task["id"], timeout=120) # solved or failed print(task["status"], task["solution"]) if task["status"] == "solved": # after you used the answer: did the site accept it? print(client.send_feedback(task["id"], correct=True)) ``` **Node.js** ```js import { Noptcha } from 'noptcha'; const client = new Noptcha(); const params = { website_url: "https://www.google.com/recaptcha/api2/demo", website_key: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", }; let task = await client.createTask('recaptcha.v2', params, { metadata: { order: 'A-17' } }); console.log(task.id, task.status); // usually "processing" task = await client.waitForTask(task.id, { timeoutMs: 120_000 }); // solved or failed console.log(task.status, task.solution); if (task.status === 'solved') { // after you used the answer: did the site accept it? console.log(await client.sendFeedback(task.id, true)); } ``` `metadata` and `callback_url` (`callbackUrl` in Node.js) are passed through as in the [API](https://noptcha.com/docs/native-api/tasks.md). The clients also cover `GET /v1/balance`, `/v1/me`, `/v1/types` and `/v1/usage` (`get_balance`, `get_me`, `list_types`, `get_usage`; `getBalance`, `getMe`, `listTypes`, `getUsage` in Node.js), and the public `GET /v1/catalog` (`get_catalog`, `getCatalog`), which never sends the key: `Noptcha.public()` makes a client without a key for it. In Node.js `getCatalog` sends only `Accept`, so it also works in a browser without a CORS preflight. ## Errors Each [error code](https://noptcha.com/docs/errors.md) has its own error class (`InvalidParamsError`, `InsufficientBalanceError`, `RateLimitError`, …), grouped by HTTP status (`PaymentRequiredError`, `TooManyRequestsError`, …), all derived from `NoptchaError`. API errors carry the status, the `code`, the `detail`, the request ID and `Retry-After`. Codes added to the API later map to the class of their HTTP status. **Python** ```python from noptcha import InvalidParamsError, Noptcha client = Noptcha() try: client.create_task("recaptcha.v2", { "website_url": "https://www.google.com/recaptcha/api2/demo", }) # website_key is missing except InvalidParamsError as e: for err in e.errors: print(err["pointer"], err["message"]) print("request id:", e.request_id) # quote it when you contact support ``` **Node.js** ```js import { InvalidParamsError, Noptcha } from 'noptcha'; const client = new Noptcha(); try { await client.createTask('recaptcha.v2', { website_url: "https://www.google.com/recaptcha/api2/demo", }); // website_key is missing } catch (err) { if (!(err instanceof InvalidParamsError)) throw err; for (const e of err.errors) console.log(e.pointer, e.message); console.log('request id:', err.requestId); // quote it when you contact support } ``` | Class | Raised when | | --- | --- | | `TaskFailedError` | `solve`: the task ended without a solution; `code` is the task’s `error.code` | | `WaitTimeoutError` | `solve`, `wait_for_task` / `waitForTask`: the task was not final in time | | `OperationCancelledError` | `solve`, `wait_for_task` / `waitForTask` (and `createTask` in Node.js) were cancelled | | `APIConnectionError`, `APITimeoutError` | no response (network error, timeout) after the retries | | `APIError` and its subclasses | the API answered with an error status | | `WebhookVerificationError` | a webhook’s signature or timestamp is not valid | ## Webhooks Both libraries verify [webhook](https://noptcha.com/docs/webhooks.md) signatures as described there: the raw body, any of several `v1,` signatures, and a timestamp at most 5 minutes from your clock. `verify(body, headers)` returns the parsed event; the one-off form takes the same order after the secret: `verify_webhook(secret, body, headers)` / `verifyWebhook(secret, body, headers)`. **Python** ```python import os from noptcha import Webhook, WebhookVerificationError webhook = Webhook(os.environ["NOPTCHA_WEBHOOK_SECRET"]) # whsec_... def handle(headers, body): """headers: the request headers; body: the raw request body (bytes). Returns the status to answer.""" try: event = webhook.verify(body, headers) except WebhookVerificationError: return 401 if event["type"] == "task.solved": print("solved:", event["data"]["id"], event["data"]["solution"]) return 204 # acknowledge every event, including types you do not handle ``` **Node.js** ```js import { Webhook, WebhookVerificationError } from 'noptcha'; const webhook = new Webhook(process.env.NOPTCHA_WEBHOOK_SECRET); // whsec_... // headers: the request headers; body: the raw request body (Buffer or string). Returns the status to answer. export async function handle(headers, body) { let event; try { event = await webhook.verify(body, headers); } catch (err) { if (err instanceof WebhookVerificationError) return 401; throw err; } if (event.type === 'task.solved') console.log('solved:', event.data.id, event.data.solution); return 204; // acknowledge every event, including types you do not handle } ``` > **Note:** Pass the body exactly as received (bytes or the raw string), not a parsed and re-serialized object, or the signature will not match. In Express, for example, use `express.raw({ type: 'application/json' })` for this route. ## Versions The libraries follow [Semantic Versioning](https://semver.org/). New task types, fields and error codes arrive in minor releases; the API stays backward compatible within `/v1` ([Versioning](https://noptcha.com/docs/versioning.md)). The compatibility API and the 2captcha legacy API are not covered: they exist for software that already speaks those protocols. --- # Keys and authentication > Noptcha API keys (nop_live_ and nop_test_), where to send them, scopes and restrictions. Source: https://noptcha.com/docs/authentication/ Every request is authenticated with an API key of your organization. ## Key format | Prefix | Kind | | --- | --- | | `nop_live_` | Live key: real solving, charged to your balance | | `nop_test_` | Test key: runs in the [sandbox](https://noptcha.com/docs/sandbox.md), never charged | After the prefix come 36 random characters and a 4-character checksum (letters and digits). The prefix lets secret scanners recognise leaked keys; the checksum lets us reject mistyped keys at once. A key is shown **once**, when you create it in the console. We store only a hash; the console shows the prefix and the last four characters. If you lose a key, revoke it and create a new one. ## Where the key goes | API | How | | --- | --- | | Native API (v1) | `Authorization: Bearer nop_live_…` header | | Compatibility API | `clientKey` field in the JSON body | | 2captcha legacy API | `key` parameter; prefer POST, see [below](#keep-keys-out-of-urls) | The key is checked before anything else in the request, so an invalid key is reported even when other fields are wrong. ## Scopes | Scope | Allows | | --- | --- | | `tasks:write` | creating tasks, reading their results, reporting answers | | `balance:read` | `GET /v1/balance`, `getBalance` | | `usage:read` | `GET /v1/usage` | `GET /v1/me` and `GET /v1/types` work with any valid key. A missing scope is `403 forbidden` in v1 and `ERROR_KEY_DOES_NOT_EXIST` in the compatibility API. ## Restrictions A key can be limited to: - **task types**: only the listed types can be created (`403 forbidden` / `ERROR_TASK_NOT_SUPPORTED` otherwise); - **client IP ranges** (CIDR): requests from other addresses get `403 forbidden` / `ERROR_IP_NOT_ALLOWED`; - **a budget per period**: when it is used up, task creation fails with `402 budget_exceeded` / `ERROR_ZERO_BALANCE`; - **an expiry date**: 7 days before it, a [`key.expiring` webhook](https://noptcha.com/docs/webhooks.md#account-events) and an email to owners and admins warn you. `GET /v1/me` shows the restrictions, the budget and how much of it is used, and the rate limits of the key. ## Invalid keys and IP blocking After 50 requests with invalid keys from one IP address within 10 minutes (an IPv6 /64 counts as one address; an IPv6 /48 is blocked after 500), that address is blocked for the rest of the 10 minutes for keys we do not already know: v1 answers `429 rate_limited`, the compatibility API `ERROR_IP_BLOCKED`. Valid keys keep working from that address. ## Keep keys out of URLs Send keys in headers or request bodies. The 2captcha legacy protocol allows the key in the query string; URLs end up in proxy logs, security tools and browser history, so use POST with a form or JSON body there too. If a key leaks, revoke it in the console and create a new one. --- # Sandbox (test keys) > Noptcha test keys (nop_test_) return fixed answers without charges; magic values simulate failures, timeouts, proxy errors and slow tasks. Source: https://noptcha.com/docs/sandbox/ Tasks created with a **test key** (`nop_test_…`) never reach a solver, are never charged and never appear in usage. They behave like real tasks otherwise: same endpoints, same validation, same response shapes. Use them to build and test integrations, and to run the examples in this documentation. ## Answers | Type | Sandbox solution | | --- | --- | | Token types (reCAPTCHA, hCaptcha, Turnstile) | `token` = `NOPTCHA_TEST_TOKEN_`, `user_agent` = `Mozilla/5.0 (Noptcha test)` | | `funcaptcha` | only `token` | | `cloudflare.challenge` | `cookies.cf_clearance` = `NOPTCHA_TEST_TOKEN_`, `user_agent` | | `image.text` | `text` = `noptcha` | | Image grids and `image.funcaptcha` | `objects` = `[0]` | Each [task type page](https://noptcha.com/docs/task-types.md) shows the exact sandbox answer in both APIs. The compatibility API returns the same answer in its own field names (for example `gRecaptchaResponse`). ## Simulating outcomes Put a magic value in `website_key` (compatibility API: `websiteKey`), `website_public_key` (`websitePublicKey`) or `question`: | Value starts with | Outcome | | --- | --- | | `test-unsolvable` | fails with `unsolvable` (`ERROR_CAPTCHA_UNSOLVABLE`) | | `test-timeout` | fails with `timeout` (`ERROR_CAPTCHA_UNSOLVABLE`) | | `test-proxy-error` | fails with `proxy_error` (`ERROR_PROXY_CONNECT_REFUSED`) | | `test-slow-` | stays `processing` for that many seconds (at most 90), then succeeds | In the sandbox the task is always created, and a failure is reported when you read the task: `getTaskResult`, `res.php`, `GET /v1/tasks/{id}`, or the response of `POST /v1/tasks` when it waits. Live tasks of some types can also fail in the create response itself (see [Solving a task](https://noptcha.com/docs/compatibility-api.md#solving-a-task)); handle both. A `test-slow-5` task is a good way to exercise polling and `Prefer: wait`. ## Limits The sandbox has its own limits per organization, on top of the per-key limits ([Rate limits](https://noptcha.com/docs/limits.md)): | | Limit | | --- | --- | | Tasks created | 10 per second (`429 rate_limited` / `ERROR_NO_SLOT_AVAILABLE`) | | Tasks still processing (`test-slow-…`) | 20 (`429 concurrency_limited`) | | Synchronous waits | 20 per API server; more return `202` at once | These are starting values and may change. ## What else is different - Sandbox tasks carry `"sandbox": true` in v1; the compatibility API returns an empty `ip`. - Results are kept for 10 minutes, like live tasks. - Test keys see only sandbox tasks and live keys only live tasks. - `GET /v1/usage` with a test key returns an empty report with `"sandbox": true`. - Balance endpoints return your organization’s real balance, which test tasks never change. --- # 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 @- < 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 @- < The Noptcha REST API under /v1/ - authentication, conventions, the task object, errors and the list of endpoints. Source: https://noptcha.com/docs/native-api/ The native API is REST + JSON under `https://api.noptcha.com/v1/`. Compared with the compatibility API it can wait for a result in the same request, retries safely with idempotency keys, and reports errors with HTTP status codes. ## Authentication ```http Authorization: Bearer nop_live_... ``` Use a `nop_test_…` key for the [sandbox](https://noptcha.com/docs/sandbox.md). See [Keys and authentication](https://noptcha.com/docs/authentication.md). ## Endpoints | Endpoint | What it does | Scope | | --- | --- | --- | | [`POST /v1/tasks`](https://noptcha.com/docs/api-reference/create-task.md) | Create a task, optionally wait for the result | `tasks:write` | | [`GET /v1/tasks/{id}`](https://noptcha.com/docs/api-reference/get-task.md) | Get a task, optionally long-poll with `?wait=` | `tasks:write` | | [`POST /v1/tasks/{id}/feedback`](https://noptcha.com/docs/api-reference/send-task-feedback.md) | Report whether the answer worked | `tasks:write` | | [`GET /v1/balance`](https://noptcha.com/docs/api-reference/get-balance.md) | Balance | `balance:read` | | [`GET /v1/types`](https://noptcha.com/docs/api-reference/list-types.md) | Task types and prices for your organization | any valid key | | [`GET /v1/me`](https://noptcha.com/docs/api-reference/get-me.md) | The key itself: organization, scopes, restrictions, budget, limits | any valid key | | [`GET /v1/usage`](https://noptcha.com/docs/api-reference/get-usage.md) | Usage and spending by hour or day | `usage:read` | | [`GET /v1/catalog`](https://noptcha.com/docs/api-reference/get-catalog.md) | Public task types and list prices | none | Details: [Tasks](https://noptcha.com/docs/native-api/tasks.md) and [Account](https://noptcha.com/docs/native-api/account.md). ## Conventions - Request and response bodies are JSON. Clients must **ignore unknown fields** and accept unknown values (new error codes, task types, statuses); see [Versioning](https://noptcha.com/docs/versioning.md). - Every response carries `Noptcha-Request-Id`; include it when you contact support. - Times are RFC 3339 in UTC, always with exactly three fractional digits (milliseconds) and a `Z` suffix: `2026-11-05T08:00:00.120Z`. - Money is a decimal string with 6 places plus a currency: `{"amount": "0.000900", "currency": "USD"}`. - IDs are strings. Task IDs are 53-bit integers written as strings (the compatibility API returns the same number as an integer). - An unknown path, or a known path with a method it does not support, is `404 not_found` (not 405). ## The task object ```json { "id": "7259140378796032", "type": "recaptcha.v2", "status": "solved", "params": { "website_url": "https://example.com/login", "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" }, "solution": { "token": "03AFcWeA…", "user_agent": "Mozilla/5.0 …" }, "error": null, "cost": { "amount": "0.000900", "currency": "USD" }, "created_at": "2026-11-05T08:00:00.123Z", "completed_at": "2026-11-05T08:00:07.456Z", "metadata": { "order": "A-17" } } ``` (The amount above is illustrative, not a price.) - `status` is `processing`, `solved` or `failed`. A failed task has `error: {code, message}` and costs zero ([task failure codes](https://noptcha.com/docs/errors.md#task-failures)). - `params` are the parameters you sent, with secrets removed (proxy login and password; cookies where the type marks them sensitive) and long strings such as images replaced by `"[N bytes]"`. - `solution` depends on the type: see its [task type page](https://noptcha.com/docs/task-types.md). - `metadata` is returned as you sent it. - Tasks created with a test key have `"sandbox": true`. - A task can be read for 10 minutes after it completes. Full schema: [`Task`](https://noptcha.com/docs/api-reference/schemas.md#Task). ## Errors Errors are RFC 9457 problem details (`application/problem+json`) with a machine-readable `code`: ```json { "type": "https://noptcha.com/docs/errors/insufficient_balance", "title": "Insufficient balance", "status": 402, "code": "insufficient_balance", "detail": "Available $0.000300, task price $0.000900.", "request_id": "req_…" } ``` `422 invalid_params` adds `errors`, a list of `{pointer, message}` where `pointer` is a JSON Pointer from the root of the request body (`/params/website_key`); problems with an image also carry a machine-readable `reason` (`image_too_large`, `image_format`, `image_base64`, `image_empty`; see [Images](https://noptcha.com/docs/limits.md#images)). All codes: [Errors](https://noptcha.com/docs/errors.md). `429` and `503` responses carry `Retry-After` (seconds): wait that long before retrying. --- # v1: tasks > Create tasks with the Noptcha v1 API - synchronous waiting with Prefer, idempotency keys, metadata, long polling and feedback. Source: https://noptcha.com/docs/native-api/tasks/ ## Create a task `POST /v1/tasks` with `type` (a [task type](https://noptcha.com/docs/task-types.md)), `params` (that type’s parameters) and optional `metadata`: **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key 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 @- < setTimeout(r, 1000 * 2 ** attempt)); // then the same request again } } console.log(res.status, res.headers.get('idempotent-replayed'), await res.json()); ``` - Keys are kept for 24 hours per organization. - The same key with a different body (any difference in the bytes) is `409 idempotency_conflict`; so is a retry while the first request with that key is still being admitted (retry shortly). - If the first request did not create a task (insufficient balance, no capacity, invalid parameters…), the key is released and you can use it again. - After the task’s result has expired (10 minutes after completion), a replay is `404 not_found`; the task is not created again. Use a new key. ## Metadata `metadata` is your own string key-value pairs, returned with the task: at most 16 keys; keys 1–64 characters, values at most 256 characters. A value that is not a string is `422 invalid_params` with pointer `/metadata/`. ## Proxies Pass `proxy` in `params` for types that accept one; 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/v1/tasks" \ -H "Authorization: Bearer $NOPTCHA_KEY" \ -H 'Content-Type: application/json' \ -d @- <= 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: "turnstile", params: { website_url: "https://example.com/signup", website_key: "1x00000000000000000000AA", action: "signup", }, }), }); 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); ``` --- # v1: account > Balance, task types and prices, key information and usage reports in the Noptcha v1 API. Source: https://noptcha.com/docs/native-api/account/ ## Balance `GET /v1/balance` (scope `balance:read`): **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key curl -s "https://api.noptcha.com/v1/balance" \ -H "Authorization: Bearer $NOPTCHA_KEY" ``` **Python** ```python import os import requests API = "https://api.noptcha.com" KEY = os.environ["NOPTCHA_KEY"] r = requests.get( f"{API}/v1/balance", headers={ "Authorization": f"Bearer {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}/v1/balance`, { method: 'GET', headers: { Authorization: `Bearer ${KEY}`, }, }); console.log(res.status, await res.json()); ``` | Field | Meaning | | --- | --- | | `available` | What new tasks can use: `cash` + `promo` + `credit_limit` − `held` | | `cash` | Money you paid in | | `promo` | Promotional credit | | `held` | Reserved for tasks in flight | | `credit_limit` | Credit agreed with us separately; zero unless you have such an agreement (accounts are prepaid by default) | All amounts are decimal strings with 6 places; `currency` is `USD`. ## Task types and prices `GET /v1/types` returns the types your organization can use, with their deadline, variants and the price of each variant for your organization. Any valid key can call it. **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key curl -s "https://api.noptcha.com/v1/types" \ -H "Authorization: Bearer $NOPTCHA_KEY" ``` **Python** ```python import os import requests API = "https://api.noptcha.com" KEY = os.environ["NOPTCHA_KEY"] r = requests.get( f"{API}/v1/types", headers={ "Authorization": f"Bearer {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}/v1/types`, { method: 'GET', headers: { Authorization: `Bearer ${KEY}`, }, }); console.log(res.status, await res.json()); ``` `data` is an empty list when no type is available. `price_version` identifies the price list the prices come from. ### Public catalog `GET /v1/catalog` lists every public task type with its list price, in the same shape, and needs no key (a key sent along is ignored). It is meant for price tables and for tools that have no key yet; use `GET /v1/types` for the prices that apply to your organization. ```sh curl https://api.noptcha.com/v1/catalog ``` The response also has `currency` (`USD`) and `placeholder` (`true` only on test deployments, where the prices are not real). It may be cached for 5 minutes (`Cache-Control: public, max-age=300`) and carries an `ETag`: send it back in `If-None-Match` to get `304 Not Modified`. Any website may read it from the browser (`Access-Control-Allow-Origin: *`, on error responses too). There is no CORS preflight: send a simple request (a plain `fetch(url)`, or only safelisted headers such as `Accept`); a request with `Authorization` or another custom header is blocked by the browser. The rate limit is per client IP (IPv6: per /64, and per /48 at ten times the rate), see [Limits](https://noptcha.com/docs/limits.md). Requests are limited per client IP: 60 per minute, bursts of 30 (starting values); beyond that `429 rate_limited` with `Retry-After`. ## The key itself `GET /v1/me` shows the organization, the key (prefix and last four characters, mode, scopes, allowed types and IP ranges, expiry), its budget and how much of it is used in the current period, and the rate limits that apply. Any valid key can call it; it never returns the key itself. **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key curl -s "https://api.noptcha.com/v1/me" \ -H "Authorization: Bearer $NOPTCHA_KEY" ``` **Python** ```python import os import requests API = "https://api.noptcha.com" KEY = os.environ["NOPTCHA_KEY"] r = requests.get( f"{API}/v1/me", headers={ "Authorization": f"Bearer {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}/v1/me`, { method: 'GET', headers: { Authorization: `Bearer ${KEY}`, }, }); console.log(res.status, await res.json()); ``` For test keys `limits.sandbox` shows the [sandbox limits](https://noptcha.com/docs/sandbox.md#limits) and the budget’s `used` is always zero. ## Usage `GET /v1/usage` (scope `usage:read`) reports solved (billed) and unbilled tasks and the amounts charged, by hour or day and by type or key: **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key curl -s "https://api.noptcha.com/v1/usage?granularity=day&group_by=type" \ -H "Authorization: Bearer $NOPTCHA_KEY" ``` **Python** ```python import os import requests API = "https://api.noptcha.com" KEY = os.environ["NOPTCHA_KEY"] r = requests.get( f"{API}/v1/usage", params={ "granularity": "day", "group_by": "type", }, headers={ "Authorization": f"Bearer {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}/v1/usage?granularity=day&group_by=type`, { method: 'GET', headers: { Authorization: `Bearer ${KEY}`, }, }); console.log(res.status, await res.json()); ``` | Parameter | Values | Default | | --- | --- | --- | | `granularity` | `hour`, `day` | `day` | | `group_by` | `type`, `key` | `type` | | `from`, `to` | RFC 3339 or `YYYY-MM-DD`, UTC; `to` is exclusive | the last 24 hours (`hour`) or 30 days (`day`) | - Ranges are rounded to whole hours or days. At most 7 days by hour and 92 days by day; `group_by=key` covers only the last 90 days (an earlier `from` is moved forward, and the response’s `from` shows the effective start). - At most 10,000 rows; a larger request is `400 invalid_request` (narrow the range or use days). - Amounts are what was actually deducted from your balance, including corrections. `data` lists only periods with activity; `totals` sums them. With `group_by=key`, a row with `"key": null` holds any amount not attributed to a key (usually zero). - Limited to 20 requests per minute per organization (bursts of 5), with `RateLimit-Policy` and `RateLimit` headers. - Test keys get an empty report with `"sandbox": true`. These ranges and limits are starting values and may change. --- # 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|`, `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|` | 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||` (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. --- # Webhooks > Get a task's final result and account events POSTed to your server. Callback URLs and console endpoints, event payloads, signature verification in Python, Node.js and Go, retries and limits. Source: https://noptcha.com/docs/webhooks/ Instead of polling, Noptcha can POST a task’s final result to your server. Organization endpoints can also receive [account events](#account-events): a low balance, top-ups, refunds, disputes and API keys about to expire. There are two ways to receive webhooks, and they can be combined. | | Per-task callback | Organization endpoint | | --- | --- | --- | | Set up | `callback_url` (v1) or `callbackUrl` ([compatibility API](https://noptcha.com/docs/compatibility-api.md)) in the create request | In the console: add an endpoint, choose the events | | Events | That task’s final result (v1: a `task.solved` or `task.failed` event; compatibility API: the `getTaskResult` body) | `task.solved`, `task.failed`, and the [account events](#account-events) | | Keys | Live and test keys | Task events: live-key tasks only; account events do not depend on the key. Send a test event from the console to try an endpoint | | Signed with | Your organization’s default webhook secret (console) | The endpoint’s own secret | Webhooks are an addition to, not a replacement for, reading the result: results are kept for 10 minutes, and a message can be late or lost, so keep [`GET /v1/tasks/{id}`](https://noptcha.com/docs/native-api/tasks.md) as a fallback. ## Per-task callbacks Add the URL when you create the task: ```json {"type": "recaptcha.v2", "params": {"…": "…"}, "callback_url": "https://example.com/hooks/noptcha"} ``` The compatibility API takes the same URL as `callbackUrl` next to `clientKey`. `null` and an empty string mean no callback. A URL that breaks the [rules below](#callback-url-rules) is rejected when you create the task (v1: `422 invalid_params` with pointer `/callback_url`; compatibility API: `ERROR_INCORRECT_SESSION_DATA`), and no task is created or charged. A `callback_url` that is not a string is `400 invalid_request`. ## Organization endpoints In the console you can add endpoints, pick the events each one receives, see and rotate its secret, disable it, and look at its recent deliveries. A new endpoint in the console starts with only the task events ticked: tick the account events you want, so a receiver that only handles task bodies is not sent events it does not know. Each endpoint has its own secret (`whsec_…`), and an organization can have up to 10 endpoints. Adding an endpoint, changing its address, and showing or rotating any signing secret (including the default one) ask you to confirm again (step-up). An endpoint that fails continuously for 24 hours is disabled; re-enable it in the console once your server is back. From the console you can send a test event to an endpoint: it delivers one `webhook.test` event (also to a disabled endpoint), with a new `msg_test_…` `webhook-id` each time. The endpoint’s delivery history in the console (kept for 7 days) shows the result of every attempt: attempt number, HTTP status, time taken, and your host name (not the path or query string). Deliveries of per-task callbacks are listed there too. Test events and resends together are limited to 30 per organization per 10 minutes. For about 15 minutes after a task message was first sent to an endpoint (about 25 hours for an account event), you can resend it from the delivery history (same `webhook-id`). After that the console answers `410 payload_expired`: the message body is no longer kept. For a task, read it with `GET /v1/tasks/{id}` while its result is still kept (10 minutes), or use the result you stored. ## Events | Event | Sent when | `data` | | --- | --- | --- | | `task.solved` | A task finished with a solution | The task object, exactly as `GET /v1/tasks/{id}` returns it | | `task.failed` | A task ended without a solution (`data.status` is `failed`, `data.error` says why) | The same | | `balance.low` | The balance fell below your alert threshold | `balance`, `threshold`, `currency` | | `payment.succeeded` | A top-up was credited to the balance | The payment: `id`, `amount`, `currency`, `created_at`, `credited_at` | | `payment.refunded` | A refund of a top-up succeeded | `id`, `payment_id`, `amount`, `currency`, `refunded_at` | | `payment.disputed` | A dispute (chargeback) was opened on a top-up | `id`, `payment_id`, `amount`, `currency`, `opened_at` | | `key.expiring` | An API key with an expiry date expires within 7 days | `id`, `name`, `prefix`, `last4`, `mode`, `expires_at` | | `webhook.test` | You request a test delivery for an endpoint in the console | `endpoint_id` and `message` | The account events are only sent to organization endpoints that subscribe to them, never to a task’s callback URL. New event types can be added at any time: ignore events you do not know and answer them with `2xx`. ### Body The body of a v1 callback and of every endpoint message is the event envelope: ```json { "type": "task.solved", "timestamp": "2026-11-05T08:00:07.456Z", "data": { "id": "7259140378796032", "type": "recaptcha.v2", "status": "solved", "solution": {"token": "03AFcWeA…"}, "error": null, "cost": {"amount": "0.001000", "currency": "USD"}, "created_at": "2026-11-05T08:00:01.120Z", "completed_at": "2026-11-05T08:00:07.456Z" } } ``` (Shortened; `data` has all the fields of the [task object](https://noptcha.com/docs/native-api/tasks.md).) `timestamp` is when the task became final. The time of each delivery attempt is in the `webhook-timestamp` header. The compatibility API’s `callbackUrl` receives the same JSON as `getTaskResult` for the final result, plus the integer `taskId`: ```json {"errorId": 0, "status": "ready", "solution": {"gRecaptchaResponse": "03AFcWeA…"}, "taskId": 7259140378796032, "…": "…"} ``` They are signed the same way; verify them if your client can. If it cannot, treat the callback only as a signal and confirm the result with `getTaskResult`. ### Account events All five share the envelope above (`type`, `timestamp`, `data`). Amounts are strings in US dollars, as in the task object’s `cost`. Each event has a fixed `webhook-id` (below), so a retry or a repeated scan never produces a second message. An endpoint only receives events that happen after it subscribed. **`balance.low`**: after a charge, your balance (cash plus promotional credit) is below the alert threshold that owners, admins and billing members set on the console’s Billing page (0 turns the alert off). The balance is read again just before the message is created: if it is back at or above the threshold (for example you just topped up), nothing is sent. At most one message per organization every 24 hours: while the balance stays low you get one a day, and a top-up does not restart the 24 hours. Raising the threshold when it was already set does. `webhook-id` is `msg_bl__`. Test-key tasks cost nothing and never trigger it. ```json { "type": "balance.low", "timestamp": "2026-11-05T08:00:07.456Z", "data": {"balance": "4.500000", "threshold": "5.000000", "currency": "USD"} } ``` **`payment.succeeded`**, **`payment.refunded`**, **`payment.disputed`**: one message per top-up credited (`webhook-id` `msg_ps_`), per successful refund (`msg_pr_`; a top-up refunded in two parts sends two) and per dispute opened (`msg_pd_`; you are not sent another when it is resolved). `payment_id` is the `tp_…` ID shown for the top-up in the console. The bodies do not say how the payment was made. ```json { "type": "payment.succeeded", "timestamp": "2026-11-05T08:00:07.456Z", "data": { "id": "tp_3k9x0q2m8w1v7c5b4n6z", "amount": "20.000000", "currency": "USD", "created_at": "2026-11-05T07:58:40.120Z", "credited_at": "2026-11-05T08:00:07.456Z" } } ``` ```json { "type": "payment.refunded", "timestamp": "2026-11-06T10:12:00.000Z", "data": { "id": "41", "payment_id": "tp_3k9x0q2m8w1v7c5b4n6z", "amount": "5.000000", "currency": "USD", "refunded_at": "2026-11-06T10:12:00.000Z" } } ``` `payment.disputed` has the same shape as `payment.refunded`, with `opened_at` instead of `refunded_at` and the amount in dispute. **`key.expiring`**: an active API key that has an expiry date will expire within 7 days. Keys that are still working only for the grace period after a rotation are not included. One message per key and expiry date: changing the key’s expiry date sends a new one. `webhook-id` is `msg_ke__`; `timestamp` is when the upcoming expiry was noticed. Test keys are included (`data.mode` is `live` or `test`). ```json { "type": "key.expiring", "timestamp": "2026-11-05T08:05:00.000Z", "data": { "id": "12", "name": "ci", "prefix": "nop_live_ab12", "last4": "wxyz", "mode": "live", "expires_at": "2026-11-10T00:00:00.000Z" } } ``` **Email as well.** Whether or not you have an endpoint, `balance.low` is also emailed to the organization’s owners, admins and billing members, and `key.expiring` to its owners and admins (members with a verified email address). Payment events are not emailed. Owners and admins are also emailed when an endpoint is disabled for failing. **Trying them out.** Test keys cannot trigger account events, except `key.expiring` for a test key that has an expiry date. To check your receiver, send a `webhook.test` event from the console, which also exercises the signature. You can also resend an account event from the delivery history. ### Headers | Header | Meaning | | --- | --- | | `webhook-id` | Message ID. The same for every retry and manual resend of the message. Examples: `msg_t7259140378796032` (endpoint), `msg_t7259140378796032_cb` (live task callback), `msg_s7259140378796032_cb` (sandbox task callback), `msg_test_…` (test delivery; new for each test); account events are listed [above](#account-events) | | `webhook-timestamp` | Unix seconds when this attempt was sent | | `webhook-signature` | One or more space-separated `v1,` entries (there can be several at any time, for example during a secret rotation) | | `Content-Type` | `application/json` | ## Verifying the signature Always verify before you trust a message: anyone who knows your URL could POST to it. The scheme is [Standard Webhooks](https://www.standardwebhooks.com/), so the libraries listed there work; or verify it yourself, it takes a few lines: 1. Take the **raw** request body, byte for byte. Do not parse and re-serialize it. 2. The signed content is `..`. 3. The key is the secret without the `whsec_` prefix, base64-decoded (32 bytes). 4. The signature is `base64(HMAC-SHA256(key, signed content))`. 5. `webhook-signature` holds one or more space-separated `v1,` entries. Accept the message if **any** entry matches, comparing in constant time. There can be several entries at any time: the default secret used for per-task callbacks usually has a next secret signing alongside it, and during a rotation both the new and the previous secret sign. 6. Reject messages whose `webhook-timestamp` is more than a few minutes away from your clock (the examples use 5 minutes). Every retry is signed afresh with a new timestamp. > **Caution:** Keep the secret out of source code and logs. After a rotation the previous secret keeps signing for 24 hours, so you can deploy the new secret without dropping messages. Rotating again within those 24 hours stops the oldest secret at once. verify.py ```python import base64, hashlib, hmac, time def verify(secret, headers, body, tolerance=300): """secret: "whsec_..."; headers: dict with lower-case names; body: raw bytes.""" msg_id = headers.get("webhook-id", "") timestamp = headers.get("webhook-timestamp", "") try: if abs(time.time() - int(timestamp)) > tolerance: return False except ValueError: return False key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() for entry in headers.get("webhook-signature", "").split(): version, _, signature = entry.partition(",") if version == "v1" and hmac.compare_digest(signature.encode(), expected.encode()): return True return False ``` verify.mjs ```js import { createHmac, timingSafeEqual } from 'node:crypto'; // secret: "whsec_..."; headers: object with lower-case names; body: the raw body as a Buffer or string. export function verify(secret, headers, body, toleranceSeconds = 300) { const id = headers['webhook-id'] ?? ''; const timestamp = headers['webhook-timestamp'] ?? ''; if (!/^[0-9]+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false; const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64'); const expected = createHmac('sha256', key).update(`${id}.${timestamp}.`).update(body).digest(); return (headers['webhook-signature'] ?? '').split(' ').some((entry) => { const [version, signature] = entry.split(','); const got = Buffer.from(signature ?? '', 'base64'); return version === 'v1' && got.length === expected.length && timingSafeEqual(got, expected); }); } ``` verify.go ```go package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "math" "strconv" "strings" "time" ) // Verify checks a webhook. secret is "whsec_..."; id, timestamp and signature are the webhook-id, // webhook-timestamp and webhook-signature headers; body is the raw request body. func Verify(secret, id, timestamp, signature string, body []byte, tolerance time.Duration) bool { sec, err := strconv.ParseInt(timestamp, 10, 64) if err != nil || math.Abs(time.Since(time.Unix(sec, 0)).Seconds()) > tolerance.Seconds() { return false } key, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_")) if err != nil { return false } mac := hmac.New(sha256.New, key) mac.Write([]byte(id + "." + timestamp + ".")) mac.Write(body) expected := mac.Sum(nil) for _, entry := range strings.Fields(signature) { version, sig, ok := strings.Cut(entry, ",") if !ok || version != "v1" { continue } if got, err := base64.StdEncoding.DecodeString(sig); err == nil && hmac.Equal(got, expected) { return true } } return false } ``` In your handler: read the raw body first, call the function with the three headers, answer `401` (or any non-2xx) when it returns false, and only then parse the JSON. ## Delivery, retries and duplicates - A `2xx` response acknowledges the message; the response body is ignored. Anything else, a redirect (not followed), or no complete response within 5 seconds is a failure. Answer quickly and do the real work afterwards. - A failed task event is retried 5, 25, 60, 150 and 300 seconds after the failure (each ±10%), and not after 10 minutes: results are only kept for 10 minutes. So a short outage is covered, a long one is not; read the task with `GET /v1/tasks/{id}` for anything you missed. - A failed account event is retried up to 8 times over 24 hours, 5 seconds, 5 minutes, 30 minutes, 1, 2, 4, 8 and 8 hours after the previous failure (each ±10%), and not after 24 hours. There is nothing to read them back from, so keep your endpoint up, or resend a missed one from the delivery history within about 25 hours. - Test-key callbacks are tried at most twice. `webhook.test` is never retried. - A message can arrive **more than once and out of order**. Deduplicate on `webhook-id` (it stays the same across retries and resends) and make your handler idempotent: for a task, `data.id` is the natural key. ## Callback URL rules - `http` or `https`, port 80, 443, 8080 or 8443, at most 2048 bytes, no user name or password, no `#fragment`. - The host must resolve to public addresses only: private, loopback, link-local and similar ranges are refused (an IP address in the URL is checked when you create the task; a name is checked again at delivery time, and a host that resolves to a non-public address is not contacted). Use a public URL; for local development expose your machine through a tunnel. ## Testing with a test key A test key (`nop_test_…`) task with a callback URL gets its callback when the sandbox result is ready (see [Sandbox](https://noptcha.com/docs/sandbox.md)), with the usual fixed answer, so you can build and test your receiver without spending money. Differences from live tasks: - the `webhook-id` starts with `msg_s` instead of `msg_t`; - a compatibility-API callback carries `"sandbox": true`; in v1 the task object in `data` carries `"sandbox": true`, as everywhere in v1. Treat such a message as a test, never as a real result; - at most two attempts, with a 2-second timeout per attempt (live: 5 seconds); - organization endpoints do not receive sandbox tasks; send a test event from the console for them. The callback is signed with your default webhook secret, so your verification code is tested too. ## Not covered - The 2captcha `pingback` parameter is not supported (`ERROR_BAD_PARAMETERS`); use `callbackUrl` or `callback_url`. --- # Task types > Every captcha type the Noptcha API solves, with its v1 type name and compatibility API names. Source: https://noptcha.com/docs/task-types/ Each task has a **type**. The native API (v1) uses the canonical names in the first column; the compatibility API accepts the common anti-captcha compatible names listed on each type's page.`GET /v1/types` returns the types your organization can use and their prices. ## All types | Type | v1 name | Family | Proxy | Compatibility names | | --- | --- | --- | --- | --- | | [Cloudflare challenge page](https://noptcha.com/docs/task-types/cloudflare-challenge.md) | `cloudflare.challenge` | Cloudflare | required | *v1 only* | | [FunCaptcha (Arkose Labs)](https://noptcha.com/docs/task-types/funcaptcha.md) beta | `funcaptcha` | Arkose Labs | optional | `FunCaptchaTask`, `FunCaptchaTaskProxyless` | | [hCaptcha](https://noptcha.com/docs/task-types/hcaptcha.md) | `hcaptcha` | hCaptcha | optional | `HCaptchaTask`, `HCaptchaTaskProxyless` | | [FunCaptcha image](https://noptcha.com/docs/task-types/image-funcaptcha.md) beta | `image.funcaptcha` | Image recognition | no | `FunCaptchaClassification` | | [AWS WAF image grid](https://noptcha.com/docs/task-types/image-grid-aws.md) beta | `image.grid.aws` | Image recognition | no | *v1 only* | | [hCaptcha image grid](https://noptcha.com/docs/task-types/image-grid-hcaptcha.md) | `image.grid.hcaptcha` | Image recognition | no | `HCaptchaClassification` | | [reCAPTCHA image grid](https://noptcha.com/docs/task-types/image-grid-recaptcha.md) | `image.grid.recaptcha` | Image recognition | no | `ReCaptchaV2Classification` | | [Image to text](https://noptcha.com/docs/task-types/image-to-text.md) | `image.text` | Image recognition | no | `ImageToTextTask` | | [reCAPTCHA v2](https://noptcha.com/docs/task-types/recaptcha-v2.md) | `recaptcha.v2` | reCAPTCHA | optional | `NoCaptchaTask`, `NoCaptchaTaskProxyless`, `RecaptchaV2Task`, `RecaptchaV2TaskProxyless` | | [reCAPTCHA v2 Enterprise](https://noptcha.com/docs/task-types/recaptcha-v2-enterprise.md) | `recaptcha.v2.enterprise` | reCAPTCHA | optional | `RecaptchaV2EnterpriseTask`, `RecaptchaV2EnterpriseTaskProxyless` | | [reCAPTCHA v3](https://noptcha.com/docs/task-types/recaptcha-v3.md) | `recaptcha.v3` | reCAPTCHA | no | `RecaptchaV3TaskProxyless` | | [reCAPTCHA v3 Enterprise](https://noptcha.com/docs/task-types/recaptcha-v3-enterprise.md) | `recaptcha.v3.enterprise` | reCAPTCHA | no | `RecaptchaV3EnterpriseTask`, `RecaptchaV3TaskProxyless` | | [Cloudflare Turnstile](https://noptcha.com/docs/task-types/turnstile.md) | `turnstile` | Cloudflare | optional | `TurnstileTask`, `TurnstileTaskProxyless` | ## Type names - **Beta** types work like the others but their parameters and success rates may still change. - Compatibility names ending in `Proxyless` ignore proxy fields; the others need a proxy (see [Proxies](https://noptcha.com/docs/proxies.md)). Names are case-insensitive. - New types can appear at any time (see [Versioning](https://noptcha.com/docs/versioning.md)). The machine-readable list is in [openapi.json](https://noptcha.com/openapi.json) (`CreateTaskRequest`, `CompatTaskType`). --- # Proxies > How to pass a proxy with a Noptcha task, which types need one, and how proxy credentials are handled. Source: https://noptcha.com/docs/proxies/ Some captchas are tied to the IP address that loads them. For those types you can (or, for some, must) give the proxy the solver should use. Each [task type page](https://noptcha.com/docs/task-types.md) says whether a proxy is optional, required or not used. ## Native API (v1) Add `proxy` to `params`: | Field | | | --- | --- | | `type` | `http`, `https`, `socks4` or `socks5` | | `host` | IP address or host name; private and loopback addresses are rejected | | `port` | 1–65535 | | `login`, `password` | optional | **curl** ```bash export NOPTCHA_KEY=nop_test_... # your test or live key curl -s -X POST "https://api.noptcha.com/v1/tasks" \ -H "Authorization: Bearer $NOPTCHA_KEY" \ -H 'Content-Type: application/json' \ -d @- < Solve Cloudflare challenge page with the Noptcha API: type cloudflare.challenge, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/cloudflare-challenge/ ## Summary | Property | Value | | --- | --- | | v1 type | `cloudflare.challenge` | | Compatibility names | *none: native API (v1) only* | | Proxy | required ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `proxy` required | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | | `user_agent` optional | `string` | Usually omit it. The User-Agent actually used is returned in the solution. min length 1 · max length 1,024 | | `wait_load` optional | `boolean` | Wait for the page's network activity to settle before returning. | | `required_cookies` optional | `array of string` | Cookie names that must all be obtained for the task to succeed, e.g. \["cf\_clearance"\]. max items 32 · unique items | | `block_image` optional | `boolean` | Do not load images of the target page (saves proxy traffic). | | `post_data` optional | `object` | If set, submitted to website\_url as an HTML form POST. max 64 keys | | `post_data.` optional | `string | number | boolean` | | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `cookies` required | `object` | Cookies after passing the challenge, name to value; usually includes cf\_clearance. | | `cookies.` optional | `string` | | | `user_agent` required | `string` | User-Agent the cookies are bound to; use it for every follow-up request. min length 1 · max length 1,024 | | `headers` optional | `object` | Response headers of the target page. | | `headers.` optional | `string` | | | `request_headers` optional | `object` | Request headers of the browser's last request to the target page. | | `request_headers.` optional | `string` | | | `content` optional | `string` | HTML of the target page after passing the challenge. | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "cloudflare.challenge", "params": { "website_url": "https://example.com/", "proxy": { "type": "http", "host": "203.0.113.7", "port": 8080, "login": "proxyuser", "password": "proxypass" }, "required_cookies": [ "cf_clearance" ] } } ``` **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 @- <= 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: "cloudflare.challenge", params: { website_url: "https://example.com/", proxy: { type: "http", host: "203.0.113.7", port: 8080, login: "proxyuser", password: "proxypass", }, required_cookies: ["cf_clearance"], }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "cookies": { "cf_clearance": "NOPTCHA_TEST_TOKEN_7259140378796032" }, "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` > **Note:** This type has no compatibility API name; use the native API (v1). --- # FunCaptcha (Arkose Labs) > Solve FunCaptcha (Arkose Labs) with the Noptcha API: type funcaptcha, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/funcaptcha/ ## Summary | Property | Value | | --- | --- | | v1 type | `funcaptcha` beta | | Compatibility names | `FunCaptchaTask`, `FunCaptchaTaskProxyless` | | Proxy | optional ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 180 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=funcaptcha` ([details](https://noptcha.com/docs/2captcha-api.md)) | > **Beta:** This type works like the others, but its parameters and success rate may still change. ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_public_key` required | `string` | The public key (data-pkey). min length 1 · max length 128 | | `api_subdomain` optional | `string` | Subdomain that serves the widget, e.g. client-api.arkoselabs.com. format: hostname · min length 1 · max length 253 | | `data` optional | `string` | Extra blob data as a JSON string, e.g. {"blob":"..."}. min length 1 · max length 16,384 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `proxy` optional | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "funcaptcha", "params": { "website_url": "https://example.com/signup", "website_public_key": "B7D8911C-5CC8-A9A3-35B0-554ACEE604DA", "api_subdomain": "client-api.arkoselabs.com" } } ``` **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 @- <= 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: "funcaptcha", params: { website_url: "https://example.com/signup", website_public_key: "B7D8911C-5CC8-A9A3-35B0-554ACEE604DA", api_subdomain: "client-api.arkoselabs.com", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `FunCaptchaTask` | required | | | `FunCaptchaTaskProxyless` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `data` | `data` | | `funcaptchaApiJSSubdomain` | `api_subdomain` | | `userAgent` | `user_agent` | | `websitePublicKey` | `website_public_key` | | `websiteURL` | `website_url` | | `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword` | `proxy` ([Proxies](https://noptcha.com/docs/proxies.md)) | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `token` | `token` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "FunCaptchaTaskProxyless", "websiteURL": "https://example.com/signup", "websitePublicKey": "B7D8911C-5CC8-A9A3-35B0-554ACEE604DA", "funcaptchaApiJSSubdomain": "client-api.arkoselabs.com" } } ``` **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 @- < 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: "FunCaptchaTaskProxyless", websiteURL: "https://example.com/signup", websitePublicKey: "B7D8911C-5CC8-A9A3-35B0-554ACEE604DA", funcaptchaApiJSSubdomain: "client-api.arkoselabs.com", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032" } ``` --- # hCaptcha > Solve hCaptcha with the Noptcha API: type hcaptcha, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/hcaptcha/ ## Summary | Property | Value | | --- | --- | | v1 type | `hcaptcha` | | Compatibility names | `HCaptchaTask`, `HCaptchaTaskProxyless` | | Proxy | optional ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=hcaptcha` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `is_invisible` optional | `boolean` | true for the invisible hCaptcha. | | `enterprise_payload` optional | `object` | Extra enterprise parameters passed to the widget as is, e.g. {"s": "..."} for reCAPTCHA Enterprise or {"rqdata": "..."} for hCaptcha. max 32 keys | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `proxy` optional | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | | `resp_key` optional | `string` | Value some sites require together with the token (respKey). | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "hcaptcha", "params": { "website_url": "https://accounts.hcaptcha.com/demo", "website_key": "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2" } } ``` **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 @- <= 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: "hcaptcha", params: { website_url: "https://accounts.hcaptcha.com/demo", website_key: "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `HCaptchaTask` | required | | | `HCaptchaTaskProxyless` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `enterprisePayload` | `enterprise_payload` | | `isInvisible` | `is_invisible` | | `rqdata` | `enterprise_payload.rqdata` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | | `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword` | `proxy` ([Proxies](https://noptcha.com/docs/proxies.md)) | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `gRecaptchaResponse` | `token` | | `userAgent` | `user_agent` | | `respKey` | `resp_key` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "HCaptchaTaskProxyless", "websiteURL": "https://accounts.hcaptcha.com/demo", "websiteKey": "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2" } } ``` **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 @- < 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: "HCaptchaTaskProxyless", websiteURL: "https://accounts.hcaptcha.com/demo", websiteKey: "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # FunCaptcha image > Solve FunCaptcha image with the Noptcha API: type image.funcaptcha, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/image-funcaptcha/ ## Summary | Property | Value | | --- | --- | | v1 type | `image.funcaptcha` beta | | Compatibility names | `FunCaptchaClassification` | | Proxy | not used | | Deadline | 60 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=base64&recaptcha=1&img_type=funcaptcha` ([details](https://noptcha.com/docs/2captcha-api.md)) | > **Beta:** This type works like the others, but its parameters and success rate may still change. ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `images` required | `array of image` | Base64 of each challenge image (at most 20). min items 1 · max items 20 | | `question` required | `string` | The question in English, e.g. Pick the image that is the correct way up. min length 1 · max length 1,024 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `objects` required | `array of integer` | Zero-based indexes of the images or tiles to select, left to right, top to bottom. max items 64 · unique items | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "image.funcaptcha", "params": { "images": [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" ], "question": "Pick the image that is the correct way up" } } ``` **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 @- <= 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: "image.funcaptcha", params: { images: [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", ], question: "Pick the image that is the correct way up", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "objects": [ 0 ] } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `FunCaptchaClassification` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `image` | `images` (a single value is wrapped in an array) | | `question` | `question` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `objects` | `objects` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "FunCaptchaClassification", "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "question": "Pick the image that is the correct way up" } } ``` **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 @- < 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: "FunCaptchaClassification", image: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", question: "Pick the image that is the correct way up", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "objects": [ 0 ] } ``` --- # AWS WAF image grid > Solve AWS WAF image grid with the Noptcha API: type image.grid.aws, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/image-grid-aws/ ## Summary | Property | Value | | --- | --- | | v1 type | `image.grid.aws` beta | | Compatibility names | *none: native API (v1) only* | | Proxy | not used | | Deadline | 60 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | > **Beta:** This type works like the others, but its parameters and success rate may still change. ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `image` required | `string` | Base64 of the whole 3x3 grid image. min length 1 · max length 1,000,000 | | `question` optional | `string` | The full question as shown on the page, e.g. Select all hats. min length 1 · max length 1,024 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `objects` required | `array of integer` | Zero-based indexes of the images or tiles to select, left to right, top to bottom. max items 64 · unique items | | `confidences` optional | `array of number` | max items 64 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "image.grid.aws", "params": { "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "question": "Select all hats" } } ``` **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 @- <= 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: "image.grid.aws", params: { image: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", question: "Select all hats", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "objects": [ 0 ] } ``` > **Note:** This type has no compatibility API name; use the native API (v1). --- # hCaptcha image grid > Solve hCaptcha image grid with the Noptcha API: type image.grid.hcaptcha, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/image-grid-hcaptcha/ ## Summary | Property | Value | | --- | --- | | v1 type | `image.grid.hcaptcha` | | Compatibility names | `HCaptchaClassification` | | Proxy | not used | | Deadline | 60 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. Give at least one of `question`, `anchors`. | Field | Type | Description | | --- | --- | --- | | `images` required | `array of image` | Base64 of each challenge image in page order. min items 1 · max items 20 | | `question` optional | `string` | The full question as shown on the page. min length 1 · max length 1,024 | | `anchors` optional | `array of image` | Base64 of the example images shown next to the question. min items 1 · max items 10 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `objects` required | `array of integer` | Zero-based indexes of the images or tiles to select, left to right, top to bottom. max items 64 · unique items | | `confidences` optional | `array of number` | max items 64 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "image.grid.hcaptcha", "params": { "images": [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" ], "question": "Please click each image containing a bicycle" } } ``` **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 @- <= 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: "image.grid.hcaptcha", params: { images: [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", ], question: "Please click each image containing a bicycle", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "objects": [ 0 ] } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `HCaptchaClassification` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `anchors` | `anchors` (a single value is wrapped in an array) | | `queries` | `images` | | `question` | `question` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `objects` | `objects` (one boolean per image: true = selected) | | `top_k` | `objects` | | `confidences` | `confidences` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "HCaptchaClassification", "queries": [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" ], "question": "Please click each image containing a bicycle" } } ``` **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 @- < 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: "HCaptchaClassification", queries: [ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", ], question: "Please click each image containing a bicycle", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "objects": [ true, false ], "top_k": [ 0 ] } ``` --- # reCAPTCHA image grid > Solve reCAPTCHA image grid with the Noptcha API: type image.grid.recaptcha, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/image-grid-recaptcha/ ## Summary | Property | Value | | --- | --- | | v1 type | `image.grid.recaptcha` | | Compatibility names | `ReCaptchaV2Classification` | | Proxy | not used | | Deadline | 60 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=base64&recaptcha=1` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `image` required | `string` | Base64 of the whole challenge image (3x3 or 4x4 grid) or of a single 1x1 tile. min length 1 · max length 1,000,000 | | `question` required | `string` | Question ID such as /m/0k4j. min length 1 · max length 1,024 | | `grid` optional | `enum` | Grid shape. Inferred from the image size when omitted. one of "1x1", "3x3", "4x4" | | `confidence` optional | `number` | 3x3 only: return every tile scoring above this threshold instead of the top 3. ≥ 0 · ≤ 1 | ## Solution The `solution` object of a solved task. Give at least one of `objects`, `has_object`. | Field | Type | Description | | --- | --- | --- | | `objects` optional | `array of integer` | Zero-based indexes of the images or tiles to select, left to right, top to bottom. max items 64 · unique items | | `has_object` optional | `boolean` | 1x1 tile: whether the tile contains the target. | | `confidences` optional | `array of number` | max items 64 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "image.grid.recaptcha", "params": { "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "question": "/m/015qff" } } ``` **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 @- <= 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: "image.grid.recaptcha", params: { image: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", question: "/m/015qff", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "objects": [ 0 ] } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `ReCaptchaV2Classification` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `body` | `image` | | `confidence` | `confidence` | | `image` | `image` | | `question` | `question` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `objects` | `objects` | | `hasObject` | `has_object` | | `confidences` | `confidences` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "ReCaptchaV2Classification", "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "question": "/m/015qff" } } ``` **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 @- < 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: "ReCaptchaV2Classification", image: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", question: "/m/015qff", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "objects": [ 0 ] } ``` --- # Image to text > Solve Image to text with the Noptcha API: type image.text, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/image-to-text/ ## Summary | Property | Value | | --- | --- | | v1 type | `image.text` | | Compatibility names | `ImageToTextTask` | | Proxy | not used | | Deadline | 60 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=base64 or method=post` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `image` required | `string` | Base64-encoded PNG, JPEG, GIF, WebP or BMP image (standard or URL-safe alphabet; a data:image/...;base64, prefix, line breaks and missing padding are accepted). At most 1,000,000 characters, 8192 pixels per side and 8,388,608 pixels in total; animated images at most 1000 frames and 8,388,608 pixels for frames x width x height; multi-picture JPEG (MPO), JPEG with more than 100 scans, malformed or truncated images are rejected. min length 1 · max length 1,000,000 | | `case_sensitive` optional | `boolean` | The answer is case-sensitive. | | `numeric` optional | `enum` | 0 no requirement; 1 digits only; 2 no digits. one of 0, 1, 2 | | `phrase` optional | `boolean` | The answer contains at least one space. | | `math` optional | `boolean` | The image is an arithmetic expression; the answer is its result. | | `min_length` optional | `integer` | Minimum answer length; 0 means no limit. ≥ 0 · ≤ 100 | | `max_length` optional | `integer` | Maximum answer length; 0 means no limit. ≥ 0 · ≤ 100 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `text` required | `string` | Recognized text. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "image.text", "params": { "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "case_sensitive": false, "numeric": 0 } } ``` **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 @- <= 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: "image.text", params: { image: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", case_sensitive: false, numeric: 0, }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "text": "noptcha" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `ImageToTextTask` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `body` | `image` | | `case` | `case_sensitive` | | `image` | `image` | | `math` | `math` | | `maxLength` | `max_length` | | `minLength` | `min_length` | | `numeric` | `numeric` | | `phrase` | `phrase` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `text` | `text` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "ImageToTextTask", "body": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "case": false, "numeric": 0 } } ``` **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 @- < 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: "ImageToTextTask", body: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", case: false, numeric: 0, } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "text": "noptcha" } ``` --- # reCAPTCHA v2 > Solve reCAPTCHA v2 with the Noptcha API: type recaptcha.v2, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/recaptcha-v2/ ## Summary | Property | Value | | --- | --- | | v1 type | `recaptcha.v2` | | Compatibility names | `NoCaptchaTask`, `NoCaptchaTaskProxyless`, `RecaptchaV2Task`, `RecaptchaV2TaskProxyless` | | Proxy | optional ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=userrecaptcha` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `is_invisible` optional | `boolean` | true for the invisible reCAPTCHA. | | `api_domain` optional | `string` | Domain the widget is loaded from, e.g. [www.recaptcha.net](http://www.recaptcha.net). Omit for the default. format: hostname · min length 1 · max length 253 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `cookies` optional | `string` | Cookies for the target site, as in a Cookie header: name1=value1; name2=value2. max length 8,192 | | `proxy` optional | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "recaptcha.v2", "params": { "website_url": "https://www.google.com/recaptcha/api2/demo", "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" } } ``` **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 @- <= 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `NoCaptchaTask` | required | | | `NoCaptchaTaskProxyless` | ignored | | | `RecaptchaV2Task` | required | | | `RecaptchaV2TaskProxyless` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `apiDomain` | `api_domain` | | `cookies` | `cookies` | | `enterprisePayload` | `enterprise_payload` | | `isEnterprise` | *accepted; only selects the type* | | `isInvisible` | `is_invisible` | | `minScore` | `min_score` | | `pageAction` | `page_action` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | | `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword` | `proxy` ([Proxies](https://noptcha.com/docs/proxies.md)) | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `gRecaptchaResponse` | `token` | | `userAgent` | `user_agent` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "RecaptchaV2TaskProxyless", "websiteURL": "https://www.google.com/recaptcha/api2/demo", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" } } ``` **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 @- < 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # reCAPTCHA v2 Enterprise > Solve reCAPTCHA v2 Enterprise with the Noptcha API: type recaptcha.v2.enterprise, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/recaptcha-v2-enterprise/ ## Summary | Property | Value | | --- | --- | | v1 type | `recaptcha.v2.enterprise` | | Compatibility names | `RecaptchaV2EnterpriseTask`, `RecaptchaV2EnterpriseTaskProxyless` | | Proxy | optional ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=userrecaptcha&enterprise=1` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `is_invisible` optional | `boolean` | true for the invisible reCAPTCHA. | | `enterprise_payload` optional | `object` | Extra enterprise parameters passed to the widget as is, e.g. {"s": "..."} for reCAPTCHA Enterprise or {"rqdata": "..."} for hCaptcha. max 32 keys | | `api_domain` optional | `string` | Domain the widget is loaded from, e.g. [www.recaptcha.net](http://www.recaptcha.net). Omit for the default. format: hostname · min length 1 · max length 253 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `cookies` optional | `string` | Cookies for the target site, as in a Cookie header: name1=value1; name2=value2. max length 8,192 | | `proxy` optional | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "recaptcha.v2.enterprise", "params": { "website_url": "https://example.com/login", "website_key": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "enterprise_payload": { "s": "S_VALUE_FROM_THE_PAGE" } } } ``` **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 @- <= 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.enterprise", params: { website_url: "https://example.com/login", website_key: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", enterprise_payload: { s: "S_VALUE_FROM_THE_PAGE", }, }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `RecaptchaV2EnterpriseTask` | required | | | `RecaptchaV2EnterpriseTaskProxyless` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `apiDomain` | `api_domain` | | `cookies` | `cookies` | | `enterprisePayload` | `enterprise_payload` | | `isEnterprise` | *accepted; only selects the type* | | `isInvisible` | `is_invisible` | | `minScore` | `min_score` | | `pageAction` | `page_action` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | | `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword` | `proxy` ([Proxies](https://noptcha.com/docs/proxies.md)) | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `gRecaptchaResponse` | `token` | | `userAgent` | `user_agent` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "RecaptchaV2EnterpriseTaskProxyless", "websiteURL": "https://example.com/login", "websiteKey": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "enterprisePayload": { "s": "S_VALUE_FROM_THE_PAGE" } } } ``` **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 @- < 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: "RecaptchaV2EnterpriseTaskProxyless", websiteURL: "https://example.com/login", websiteKey: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", enterprisePayload: { s: "S_VALUE_FROM_THE_PAGE", }, } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # reCAPTCHA v3 > Solve reCAPTCHA v3 with the Noptcha API: type recaptcha.v3, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/recaptcha-v3/ ## Summary | Property | Value | | --- | --- | | v1 type | `recaptcha.v3` | | Compatibility names | `RecaptchaV3TaskProxyless` | | Proxy | not used | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=userrecaptcha&version=v3` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `page_action` optional | `string` | The action passed to grecaptcha.execute. min length 1 · max length 256 | | `min_score` optional | `number` | Minimum score the token should reach (0.1 to 0.9). ≥ 0.1 · ≤ 0.9 | | `api_domain` optional | `string` | Domain the widget is loaded from, e.g. [www.recaptcha.net](http://www.recaptcha.net). Omit for the default. format: hostname · min length 1 · max length 253 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `cookies` optional | `string` | Cookies for the target site, as in a Cookie header: name1=value1; name2=value2. max length 8,192 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "recaptcha.v3", "params": { "website_url": "https://example.com/login", "website_key": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "page_action": "login", "min_score": 0.7 } } ``` **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 @- <= 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.v3", params: { website_url: "https://example.com/login", website_key: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", page_action: "login", min_score: 0.7, }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `RecaptchaV3TaskProxyless` | ignored | With `isEnterprise: true` it is `recaptcha.v3.enterprise`. | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `apiDomain` | `api_domain` | | `cookies` | `cookies` | | `enterprisePayload` | `enterprise_payload` | | `isEnterprise` | *accepted; only selects the type* | | `isInvisible` | `is_invisible` | | `minScore` | `min_score` | | `pageAction` | `page_action` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `gRecaptchaResponse` | `token` | | `userAgent` | `user_agent` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "RecaptchaV3TaskProxyless", "websiteURL": "https://example.com/login", "websiteKey": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "pageAction": "login", "minScore": 0.7 } } ``` **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 @- < 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: "RecaptchaV3TaskProxyless", websiteURL: "https://example.com/login", websiteKey: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", pageAction: "login", minScore: 0.7, } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # reCAPTCHA v3 Enterprise > Solve reCAPTCHA v3 Enterprise with the Noptcha API: type recaptcha.v3.enterprise, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/recaptcha-v3-enterprise/ ## Summary | Property | Value | | --- | --- | | v1 type | `recaptcha.v3.enterprise` | | Compatibility names | `RecaptchaV3EnterpriseTask`, `RecaptchaV3TaskProxyless` | | Proxy | not used | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=userrecaptcha&version=v3&enterprise=1` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `page_action` optional | `string` | The action passed to grecaptcha.execute. min length 1 · max length 256 | | `min_score` optional | `number` | Minimum score the token should reach (0.1 to 0.9). ≥ 0.1 · ≤ 0.9 | | `enterprise_payload` optional | `object` | Extra enterprise parameters passed to the widget as is, e.g. {"s": "..."} for reCAPTCHA Enterprise or {"rqdata": "..."} for hCaptcha. max 32 keys | | `api_domain` optional | `string` | Domain the widget is loaded from, e.g. [www.recaptcha.net](http://www.recaptcha.net). Omit for the default. format: hostname · min length 1 · max length 253 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `cookies` optional | `string` | Cookies for the target site, as in a Cookie header: name1=value1; name2=value2. max length 8,192 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "recaptcha.v3.enterprise", "params": { "website_url": "https://example.com/login", "website_key": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "page_action": "login" } } ``` **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 @- <= 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.v3.enterprise", params: { website_url: "https://example.com/login", website_key: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", page_action: "login", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `RecaptchaV3EnterpriseTask` | ignored | | | `RecaptchaV3TaskProxyless` | ignored | Only with `isEnterprise: true`. | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `apiDomain` | `api_domain` | | `cookies` | `cookies` | | `enterprisePayload` | `enterprise_payload` | | `isEnterprise` | *accepted; only selects the type* | | `isInvisible` | `is_invisible` | | `minScore` | `min_score` | | `pageAction` | `page_action` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `gRecaptchaResponse` | `token` | | `userAgent` | `user_agent` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "RecaptchaV3TaskProxyless", "isEnterprise": true, "websiteURL": "https://example.com/login", "websiteKey": "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", "pageAction": "login" } } ``` **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 @- < 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: "RecaptchaV3TaskProxyless", isEnterprise: true, websiteURL: "https://example.com/login", websiteKey: "6Lc_aCMTAAAAABx7u2W0WPXnVbI_v6ZdbM6rYf16", pageAction: "login", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "gRecaptchaResponse": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # Cloudflare Turnstile > Solve Cloudflare Turnstile with the Noptcha API: type turnstile, parameters, solution fields, compatibility names and runnable examples. Source: https://noptcha.com/docs/task-types/turnstile/ ## Summary | Property | Value | | --- | --- | | v1 type | `turnstile` | | Compatibility names | `TurnstileTask`, `TurnstileTaskProxyless` | | Proxy | optional ([Proxies](https://noptcha.com/docs/proxies.md)) | | Deadline | 120 seconds (starting value). A task not solved by then fails with `timeout` and is not charged. | | 2captcha legacy API | `method=turnstile` ([details](https://noptcha.com/docs/2captcha-api.md)) | ## Parameters The `params` object of `POST /v1/tasks`. Unknown fields are rejected with `422 invalid_params`. | Field | Type | Description | | --- | --- | --- | | `website_url` required | `string` | Full URL of the page that shows the captcha (http or https). format: uri · pattern: ^\[Hh\]\[Tt\]\[Tt\]\[Pp\]\[Ss\]?:// · min length 8 · max length 4,096 | | `website_key` required | `string` | The site key of the captcha widget. min length 1 · max length 512 | | `action` optional | `string` | The action option of the widget (data-action). min length 1 · max length 256 | | `cdata` optional | `string` | The cData option of the widget (data-cdata). min length 1 · max length 1,024 | | `page_data` optional | `string` | The chlPageData value of a challenge page widget. min length 1 · max length 8,192 | | `user_agent` optional | `string` | Browser User-Agent. min length 1 · max length 1,024 | | `proxy` optional | `object` | Proxy the solver must use. login and password are used in memory only and are never stored or logged. | | `proxy.type` required | `enum` | one of "http", "https", "socks4", "socks5" | | `proxy.host` required | `string` | IP address or host name. Private and loopback addresses are rejected. min length 1 · max length 253 | | `proxy.port` required | `integer` | ≥ 1 · ≤ 65535 | | `proxy.login` optional | `string` | max length 256 | | `proxy.password` optional | `string` | max length 256 | ## Solution The `solution` object of a solved task. | Field | Type | Description | | --- | --- | --- | | `token` required | `string` | Token to submit to the target site. Single use. min length 1 | | `user_agent` optional | `string` | User-Agent the token was obtained with; use it when submitting the token. min length 1 · max length 1,024 | ## Example: native API (v1) Runs as is with a test key (`nop_test_…`) in the [sandbox](https://noptcha.com/docs/sandbox.md); use a live key for real tasks. POST /v1/tasks ```json { "type": "turnstile", "params": { "website_url": "https://example.com/signup", "website_key": "1x00000000000000000000AA", "action": "signup" } } ``` **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 @- <= 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: "turnstile", params: { website_url: "https://example.com/signup", website_key: "1x00000000000000000000AA", action: "signup", }, }), }); 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); ``` In the sandbox the task is solved at once and `solution` is: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "user_agent": "Mozilla/5.0 (Noptcha test)" } ``` ## Compatibility API | Name | Proxy | Notes | | --- | --- | --- | | `TurnstileTask` | required | | | `TurnstileTaskProxyless` | ignored | | Task fields (in `task`, next to `type`) and the v1 parameter each one becomes: | Compatibility field | v1 parameter | | --- | --- | | `data` | `cdata` | | `pageAction` | `action` | | `userAgent` | `user_agent` | | `websiteKey` | `website_key` | | `websiteURL` | `website_url` | | `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword` | `proxy` ([Proxies](https://noptcha.com/docs/proxies.md)) | Solution fields in `getTaskResult`: | Compatibility field | v1 solution field | | --- | --- | | `token` | `token` | | `userAgent` | `user_agent` | ## Example: compatibility API POST /createTask ```json { "clientKey": "nop_test_…", "task": { "type": "TurnstileTaskProxyless", "websiteURL": "https://example.com/signup", "websiteKey": "1x00000000000000000000AA", "pageAction": "signup" } } ``` **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 @- < 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: "TurnstileTaskProxyless", websiteURL: "https://example.com/signup", websiteKey: "1x00000000000000000000AA", pageAction: "signup", } }); 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); ``` In the sandbox `getTaskResult` returns `"status": "ready"` and `solution`: ```json { "token": "NOPTCHA_TEST_TOKEN_7259140378796032", "userAgent": "Mozilla/5.0 (Noptcha test)" } ``` --- # Errors > Error codes of the Noptcha API: v1 problem details, task failure codes and compatibility API error codes. Source: https://noptcha.com/docs/errors/ There are two kinds of errors. A **request error** means the request was rejected: no task was created and nothing was charged. A **task failure** means the task was created but could not be solved: it ends with `status: "failed"` and is not charged. Use the codes in programs; messages may change. New codes can be added at any time ([Versioning](https://noptcha.com/docs/versioning.md)). ## Request errors (v1) The native API returns errors as RFC 9457 problem details (`application/problem+json`). `type` is`https://noptcha.com/docs/errors/`; every response carries `request_id` (also in the`Noptcha-Request-Id` header). ```json { "type": "https://noptcha.com/docs/errors/insufficient_balance", "title": "Insufficient balance", "status": 402, "code": "insufficient_balance", "detail": "Available $0.000300, task price $0.000900.", "request_id": "req_…" } ``` | code | HTTP | When | | --- | --- | --- | | [`invalid_request`](https://noptcha.com/docs/errors/invalid_request.md) | 400 | malformed body, missing field, bad parameter, `callback_url` not a string | | [`unsupported_type`](https://noptcha.com/docs/errors/unsupported_type.md) | 400 | unknown or unavailable task type | | [`unauthorized`](https://noptcha.com/docs/errors/unauthorized.md) | 401 | invalid, revoked or expired key | | [`insufficient_balance`](https://noptcha.com/docs/errors/insufficient_balance.md) | 402 | balance too low for the task's price | | [`budget_exceeded`](https://noptcha.com/docs/errors/budget_exceeded.md) | 402 | the key's budget for the period is used up | | [`forbidden`](https://noptcha.com/docs/errors/forbidden.md) | 403 | missing scope, IP not allowed, type not allowed, organization suspended | | [`not_found`](https://noptcha.com/docs/errors/not_found.md) | 404 | unknown endpoint or method; unknown, expired or foreign task; expired idempotent task | | [`idempotency_conflict`](https://noptcha.com/docs/errors/idempotency_conflict.md) | 409 | same `Idempotency-Key` with another body, or still in progress | | [`task_not_solved`](https://noptcha.com/docs/errors/task_not_solved.md) | 409 | feedback for a task that is still processing or failed | | [`request_too_large`](https://noptcha.com/docs/errors/request_too_large.md) | 413 | body over the limit | | [`invalid_params`](https://noptcha.com/docs/errors/invalid_params.md) | 422 | `params` or `metadata` do not match the schema, or `callback_url` is not allowed | | [`rate_limited`](https://noptcha.com/docs/errors/rate_limited.md) | 429 | over a rate limit, or IP blocked after invalid keys | | [`concurrency_limited`](https://noptcha.com/docs/errors/concurrency_limited.md) | 429 | too many tasks in flight | | [`internal`](https://noptcha.com/docs/errors/internal.md) | 500 | our error | | [`service_unavailable`](https://noptcha.com/docs/errors/service_unavailable.md) | 503 | no capacity or a dependency is down | `422 invalid_params` adds `errors[]` with one `{pointer, message}` per problem (at most 50, sorted by pointer; `detail` then ends with “and N more errors”); problems with an image also carry a `reason` (`image_too_large`, `image_format`, `image_base64`, `image_empty`; see [Images](https://noptcha.com/docs/limits.md#images)). ## Task failures In the native API a failed task has `status: "failed"` and `error: {code, message}`, with a cost of zero: | error.code | Meaning | | --- | --- | | `unsolvable` | the captcha could not be solved (also when no solver accepts these parameters) | | `timeout` | not solved before the type's deadline | | `proxy_error` | the proxy could not be used | | `invalid_site` | the site key or domain is invalid | | `invalid_image` | the image is empty, too big or unreadable | | `cancelled` | the task was cancelled | In the compatibility API a failed task is reported by `getTaskResult` with `errorId: 1` and an error code such as`ERROR_CAPTCHA_UNSOLVABLE` or one of the proxy codes below. ## Compatibility API Every response is HTTP 200; `errorId` is 0 on success and 1 on error, with `errorCode` and`errorDescription`. The codes are the anti-captcha compatible ones: | errorCode | When | | --- | --- | | `ERROR_KEY_DOES_NOT_EXIST` | key malformed, unknown, revoked or expired; or the key lacks the scope this endpoint needs | | `ERROR_ZERO_BALANCE` | balance too low, or the key's budget is used up | | `ERROR_IP_NOT_ALLOWED` | client IP not in the key's allowlist | | `ERROR_IP_BLOCKED` | too many invalid keys from this IP | | `ERROR_ACCOUNT_SUSPENDED` | the organization is suspended | | `ERROR_NO_SLOT_AVAILABLE` | no capacity, concurrency limit, create rate limit (also sandbox limits), temporary or internal error; retry later | | `ERROR_TASK_NOT_SUPPORTED` | unknown task type name, or type not allowed for the key | | `ERROR_TASK_ABSENT` | `task` missing or not an object (or the body could not be read) | | `ERROR_NO_SUCH_CAPCHA_ID` | `taskId` missing, unknown, expired or of another organization; report for an unsolved task | | `ERROR_INCORRECT_SESSION_DATA` | a parameter does not match the schema (`errorDescription` names it), or `callbackUrl` is not allowed | | `ERROR_RECAPTCHA_INVALID_SITEKEY` | the site key is invalid (parameter check or solver) | | `ERROR_RECAPTCHA_INVALID_DOMAIN` | the website URL, API domain or subdomain is invalid or not allowed | | `ERROR_ZERO_CAPTCHA_FILESIZE` | the image is missing or empty | | `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | the image (characters or pixels) or the request body is too large | | `ERROR_IMAGE_TYPE_NOT_SUPPORTED` | the image is not valid base64, not a PNG, JPEG, GIF, WebP or BMP image, or malformed or truncated (checked when the task is created); or the solver could not read it (task failed) | | `ERROR_CAPTCHA_UNSOLVABLE` | the task failed: unsolvable, timed out, or no solver accepts these parameters | | `ERROR_PROXY_CONNECT_REFUSED` `ERROR_PROXY_CONNECT_TIMEOUT` `ERROR_PROXY_READ_TIMEOUT` `ERROR_PROXY_BANNED` `ERROR_PROXY_TRANSPARENT` `ERROR_PROXY_HAS_NO_IMAGE_SUPPORT` `ERROR_PROXY_INCOMPATIBLE_HTTP_VERSION` | the proxy could not be used (task failed) | | `ERROR_TOKEN_EXPIRED` | the solver reported the token expired (task failed) | Two responses come from the infrastructure rather than the protocol: HTTP 403 (problem details, `forbidden`) when a request does not pass through our edge network and HTTP 500 (`internal`). Treat both as transient. The 2captcha legacy API has its own codes, see [2captcha legacy API](https://noptcha.com/docs/2captcha-api.md#errors). ## What to retry - **Retry after a wait**: `rate_limited`, `concurrency_limited`, `service_unavailable`, `internal`; `ERROR_NO_SLOT_AVAILABLE`. Honour `Retry-After` when present. - **Fix first**: everything else. Retrying an invalid request unchanged gives the same error. - **Failed tasks** are not charged; you may create a new task for the same captcha. - For `POST /v1/tasks`, send an `Idempotency-Key` so that a retry after a network error cannot create a second task ([Idempotency](https://noptcha.com/docs/native-api/tasks.md#idempotency)). --- # Rate limits and quotas > Noptcha API rate limits, concurrency, request and image size, waiting and retention limits, and how to handle 429 responses. Source: https://noptcha.com/docs/limits/ All numbers on this page are **starting values**: they may change, and limits can be raised per organization. `GET /v1/me` returns the values that apply to your key. ## Limits | Limit | Default | Over the limit: v1 | Over the limit: compatibility API | | --- | --- | --- | --- | | Creating tasks | 200 per second per key, bursts of 200 | `429 rate_limited` | `ERROR_NO_SLOT_AVAILABLE` | | Polling one task | 5 per second, bursts of 10; a long poll (`wait`) counts once | `429 rate_limited` | `processing` (not an error) | | Tasks in flight | 50 per organization and captcha family | `429 concurrency_limited` | `ERROR_NO_SLOT_AVAILABLE` | | Invalid keys | 50 per IP address in 10 minutes, then blocked for the rest of the window (IPv6: per /64; 500 per /48) | `429 rate_limited` | `ERROR_IP_BLOCKED` | | Synchronous wait | at most 90 seconds | — | — | | Request body | 1 MB for image types, 64 KB for other types | `413 request_too_large` | `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | | Image size | 8192 pixels per side and 8,388,608 (223) pixels in total per image, read from the file structure (for GIF and WebP the largest of the canvas and every frame) | `422 invalid_params`, pointer to the image (`/params/images/1`), `reason: "image_too_large"` | `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | | Animated images | at most 1000 frames, and frames × width × height at most 8,388,608 (223) per image; [see below](#images) | same as image size | same as image size | | All images of one task | frames × width × height of every image added up: at most 33,554,432 (225) | same as image size, pointer to the image that went over | same as image size | | Results | kept for 10 minutes after the task completes | `404 not_found` | `ERROR_NO_SUCH_CAPCHA_ID` | | Feedback | for solved tasks, within 10 minutes after completion | `404 not_found` (expired), `409 task_not_solved` | `ERROR_NO_SUCH_CAPCHA_ID` | | Usage reports | 20 per minute per organization, bursts of 5 | `429 rate_limited` | — | | Public catalog (`GET /v1/catalog`) | 60 per minute per client IP (IPv6: per /64), bursts of 30; IPv6 also 600 per minute per /48, bursts of 300 | `429 rate_limited` | — | ## Images Every image is checked when the task is created, before anything is charged: - **Encoding**: base64 in the standard (`+/`) or URL-safe (`-_`) alphabet, not both in one image. Missing `=` padding, line breaks, spaces and a `data:image/;base64,` prefix are accepted; the image is passed on to the solver in standard base64 without them. `=` in the middle, any other character or another `data:` prefix is rejected. - **Format**: PNG, JPEG, GIF, WebP or BMP only, complete and well-formed (for example, one PNG `IHDR` chunk at the start and an `IEND` at the end; one JPEG frame header and nothing but `0xFF` fill between segments; a GIF that ends with its trailer). Files that common encoders produce pass; files that only a lenient decoder would open do not. Multi-picture JPEG files (MPO, such as some phone camera photos) are not accepted: send a plain JPEG. A JPEG may have at most 100 scans (a progressive JPEG has one per refinement pass; common encoders write 10 or fewer): solvers decode the whole image once per scan. - **Size**: the pixel limit above. - **Animation** (GIF, APNG, animated WebP): solvers that read every frame hold every frame as a full canvas, so each frame counts as width × height. At most 1000 frames per image, and frames × width × height at most 8,388,608 (for example 1 frame of 2896 × 2896, or 1000 frames of 91 × 91). A typical animated captcha (a few dozen frames of a couple of hundred pixels) is far below this. - **Per task**: for types that take several images (`images`, `anchors`), frames × width × height of all images together at most 33,554,432, for example 20 images of 4000 × 400. The first image that goes over the total is reported. | Problem | v1 `reason` (with `422 invalid_params`) | Compatibility API | 2captcha API | | --- | --- | --- | --- | | Over 1,000,000 characters, the pixel or frame limits, or the per-task total | `image_too_large` | `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | `ERROR_TOO_BIG_CAPTCHA_FILESIZE` | | Not one of the formats above, a multi-picture JPEG, a JPEG with more than 100 scans, malformed or truncated | `image_format` | `ERROR_IMAGE_TYPE_NOT_SUPPORTED` | `ERROR_IMAGE_TYPE_NOT_SUPPORTED` | | Not valid base64 | `image_base64` | `ERROR_IMAGE_TYPE_NOT_SUPPORTED` | `ERROR_UPLOAD` | | Missing or empty | `image_empty` | `ERROR_ZERO_CAPTCHA_FILESIZE` | `ERROR_ZERO_CAPTCHA_FILESIZE` | A request with many problems lists at most 50 of them in `errors[]` (sorted by pointer); `detail` then ends with “and N more errors”. An image array over its maximum number of items is reported once at the array (`/params/images`), without checking each image. The [sandbox](https://noptcha.com/docs/sandbox.md#limits) has additional, lower limits per organization. The 2captcha legacy API counts against the same limits; batch reads (`ids=`) count each task ID against the create rate of the key. ## Rate limit headers `POST /v1/tasks` responses carry the current state of the create limit, in the structured form of the IETF draft *RateLimit header fields for HTTP* (not final yet; we will follow the RFC when it is published): ```http RateLimit-Policy: "create";q=200;w=1 RateLimit: "create";r=153;t=1 ``` `q` is the quota per window, `w` the window in seconds, `r` what remains, `t` the seconds until the quota is full again. Polls carry the `"poll"` policy only when over the limit; `GET /v1/usage` carries `"usage"`; `GET /v1/catalog` carries `"catalog"` only when over the limit. ## Handling 429 and 503 - Wait for the number of seconds in `Retry-After`, then retry. Do not retry at once. - When a long poll returns `processing` immediately, wait a second before polling again. - In the compatibility API there is no HTTP 429; back off for a few seconds on `ERROR_NO_SLOT_AVAILABLE`. - Requests that were rejected did not create a task and were not charged. ## Deadlines Each type has a deadline (60–180 seconds by default; see each [task type page](https://noptcha.com/docs/task-types.md) and `GET /v1/types`). A task not solved by then fails with `timeout` and is not charged. --- # Versioning and compatibility > How the Noptcha API changes - backward-compatible additions in v1, at least 12 months for v1 after a v2, and what clients must tolerate. Source: https://noptcha.com/docs/versioning/ ## Native API (v1) `/v1` changes only in **backward-compatible** ways. We may at any time: - add fields to responses and optional fields to requests; - add endpoints; - add values to existing fields: error codes, task types, task statuses, families, variants; - add task types and compatibility names. Your client must **ignore unknown fields and accept unknown values**. Do not reject a response because it has a field you do not know, and treat an unknown error code by its HTTP status. Changes that would break existing clients go into a new version (`/v2`). After `/v2` is released, `/v1` keeps working for **at least 12 months**. ## Compatibility API The compatibility API has no version: it follows the anti-captcha compatible protocol. New task type names and error codes can be added; existing names keep their meaning. ## What is not covered - Error messages (`detail`, `errorDescription`, `error_text`, `error.message`) are for humans and may change; use the codes. - [Limits](https://noptcha.com/docs/limits.md) are starting values and may change. - Beta task types may change their parameters and behaviour. - The sandbox’s answers are fixed per type, but new magic values may be added. ## Machine-readable description The [OpenAPI description](https://noptcha.com/openapi.json) (also as [YAML](https://noptcha.com/openapi.yaml)) is the reference for field names and types. It is checked against the implementation by automated contract tests. --- # API reference > Every operation of the Noptcha API, generated from the OpenAPI 3.1 description. Source: https://noptcha.com/docs/api-reference/ Base URL `https://api.noptcha.com` (HTTPS only). This reference is generated from our OpenAPI 3.1 description, which is checked against the implementation by contract tests. The 2captcha legacy endpoints (`/in.php`,`/res.php`) are not part of it; see [2captcha legacy API](https://noptcha.com/docs/2captcha-api.md). ## OpenAPI - [openapi.json](https://noptcha.com/openapi.json) and [openapi.yaml](https://noptcha.com/openapi.yaml): OpenAPI 3.1.0, API version 1.0.0. - [All schemas](https://noptcha.com/docs/api-reference/schemas.md). - Task parameter schemas also have their own URLs, for example [/schemas/recaptcha.v2.params.json](https://noptcha.com/schemas/recaptcha.v2.params.json) (JSON Schema 2020-12). ## Compatibility The anti-captcha compatible JSON protocol. | Endpoint | Summary | | --- | --- | | [POST `/createTask`](https://noptcha.com/docs/api-reference/compat-create-task.md) | Create a task | | [POST `/getBalance`](https://noptcha.com/docs/api-reference/compat-get-balance.md) | Get the balance | | [POST `/getTaskResult`](https://noptcha.com/docs/api-reference/compat-get-task-result.md) | Get a task's result | | [POST `/reportCorrectRecaptcha`](https://noptcha.com/docs/api-reference/compat-report-correct-recaptcha.md) | Report a correct token | | [POST `/reportIncorrectImageCaptcha`](https://noptcha.com/docs/api-reference/compat-report-incorrect-image-captcha.md) | Report a wrong image answer | | [POST `/reportIncorrectRecaptcha`](https://noptcha.com/docs/api-reference/compat-report-incorrect-recaptcha.md) | Report a wrong token | ## Tasks Create and poll tasks, report wrong answers (native API). | Endpoint | Summary | | --- | --- | | [POST `/v1/tasks`](https://noptcha.com/docs/api-reference/create-task.md) | Create a task | | [GET `/v1/tasks/{id}`](https://noptcha.com/docs/api-reference/get-task.md) | Get a task | | [POST `/v1/tasks/{id}/feedback`](https://noptcha.com/docs/api-reference/send-task-feedback.md) | Report whether an answer worked | ## Account Balance, task types and prices, the key itself, usage, the public catalog (native API). | Endpoint | Summary | | --- | --- | | [GET `/v1/balance`](https://noptcha.com/docs/api-reference/get-balance.md) | Get the balance | | [GET `/v1/catalog`](https://noptcha.com/docs/api-reference/get-catalog.md) | Get the public catalog and list prices | | [GET `/v1/me`](https://noptcha.com/docs/api-reference/get-me.md) | Describe the current key | | [GET `/v1/types`](https://noptcha.com/docs/api-reference/list-types.md) | List task types and prices | | [GET `/v1/usage`](https://noptcha.com/docs/api-reference/get-usage.md) | Get usage | --- # invalid_request > Noptcha API error invalid_request (HTTP 400): what it means and what to do. Source: https://noptcha.com/docs/errors/invalid_request/ HTTP **400**, `application/problem+json`. When: malformed body, missing field, bad parameter, `callback_url` not a string. ## What to do The request itself is malformed: the body is not a JSON object, a field is missing or has the wrong type, a header such as `Idempotency-Key` is invalid, or `callback_url` is not a string. `detail` says what is wrong. Fix the request; retrying it unchanged gives the same error. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # unsupported_type > Noptcha API error unsupported_type (HTTP 400): what it means and what to do. Source: https://noptcha.com/docs/errors/unsupported_type/ HTTP **400**, `application/problem+json`. When: unknown or unavailable task type. ## What to do The `type` is unknown or not available. Use the canonical names from [Task types](https://noptcha.com/docs/task-types.md) (for example `recaptcha.v2`); `GET /v1/types` lists the types your organization can use. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # unauthorized > Noptcha API error unauthorized (HTTP 401): what it means and what to do. Source: https://noptcha.com/docs/errors/unauthorized/ HTTP **401**, `application/problem+json`. When: invalid, revoked or expired key. ## What to do The key is malformed, unknown, revoked or expired. Send it as `Authorization: Bearer nop_live_…` (or `nop_test_…`). After 50 invalid keys from one IP address within 10 minutes, that address is blocked for the rest of the window (`rate_limited`) for keys we do not already know. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # insufficient_balance > Noptcha API error insufficient_balance (HTTP 402): what it means and what to do. Source: https://noptcha.com/docs/errors/insufficient_balance/ HTTP **402**, `application/problem+json`. When: balance too low for the task's price. ## What to do The available balance is lower than the task price. Nothing was created or charged. Top up in the console and retry. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # budget_exceeded > Noptcha API error budget_exceeded (HTTP 402): what it means and what to do. Source: https://noptcha.com/docs/errors/budget_exceeded/ HTTP **402**, `application/problem+json`. When: the key's budget for the period is used up. ## What to do The key's spending budget for the current period is used up. Nothing was created or charged. Raise the budget in the console, use another key, or wait for the next period. `GET /v1/me` shows the budget and how much is used. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # forbidden > Noptcha API error forbidden (HTTP 403): what it means and what to do. Source: https://noptcha.com/docs/errors/forbidden/ HTTP **403**, `application/problem+json`. When: missing scope, IP not allowed, type not allowed, organization suspended. ## What to do The key is valid but not allowed to do this: it lacks the scope (`tasks:write`, `balance:read`, `usage:read`), the task type is not in its allowlist, the client IP is not in its allowed ranges, or the organization is suspended. `detail` says which. Also returned when a request reaches the API without passing through our edge network. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # not_found > Noptcha API error not_found (HTTP 404): what it means and what to do. Source: https://noptcha.com/docs/errors/not_found/ HTTP **404**, `application/problem+json`. When: unknown endpoint or method; unknown, expired or foreign task; expired idempotent task. ## What to do The task does not exist, has expired (results are kept for 10 minutes after completion), or belongs to another organization; or the path or method does not exist (we return 404, not 405). A replay of an `Idempotency-Key` whose task has expired is also 404: use a new key. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # idempotency_conflict > Noptcha API error idempotency_conflict (HTTP 409): what it means and what to do. Source: https://noptcha.com/docs/errors/idempotency_conflict/ HTTP **409**, `application/problem+json`. When: same `Idempotency-Key` with another body, or still in progress. ## What to do The `Idempotency-Key` was already used with a different request body, or the first request with this key is still being admitted. In the second case retry shortly; in the first, use a new key. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # task_not_solved > Noptcha API error task_not_solved (HTTP 409): what it means and what to do. Source: https://noptcha.com/docs/errors/task_not_solved/ HTTP **409**, `application/problem+json`. When: feedback for a task that is still processing or failed. ## What to do Feedback is accepted only for solved tasks, within 10 minutes after completion. The task is still processing or has failed. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # request_too_large > Noptcha API error request_too_large (HTTP 413): what it means and what to do. Source: https://noptcha.com/docs/errors/request_too_large/ HTTP **413**, `application/problem+json`. When: body over the limit. ## What to do The request body is over the limit: 1 MB for image types, 64 KB for other types. Any request over 1 MB is rejected, whatever the method. Send smaller images. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # invalid_params > Noptcha API error invalid_params (HTTP 422): what it means and what to do. Source: https://noptcha.com/docs/errors/invalid_params/ HTTP **422**, `application/problem+json`. When: `params` or `metadata` do not match the schema, or `callback_url` is not allowed. ## What to do `params` or `metadata` do not match the schema, or `callback_url` breaks the [URL rules](https://noptcha.com/docs/webhooks.md#callback-url-rules) (pointer `/callback_url`). `errors[]` lists each problem as `{pointer, message}`, where `pointer` is a JSON Pointer from the root of the request body (for example `/params/website_key`). The parameters of each type are on its [task type page](https://noptcha.com/docs/task-types.md). See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # rate_limited > Noptcha API error rate_limited (HTTP 429): what it means and what to do. Source: https://noptcha.com/docs/errors/rate_limited/ HTTP **429**, `application/problem+json`. When: over a rate limit, or IP blocked after invalid keys. ## What to do Too many requests: over the create rate of the key, polling one task too often, the sandbox rate, or the client IP is blocked after invalid keys (`detail` says which). Wait for `Retry-After` seconds before retrying. See [Rate limits](https://noptcha.com/docs/limits.md). See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # concurrency_limited > Noptcha API error concurrency_limited (HTTP 429): what it means and what to do. Source: https://noptcha.com/docs/errors/concurrency_limited/ HTTP **429**, `application/problem+json`. When: too many tasks in flight. ## What to do The organization already has the maximum number of tasks in flight for this captcha family (50 by default). In the sandbox the limit is 20 tasks in flight per organization, across all types. Wait for some tasks to finish, then retry. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # internal > Noptcha API error internal (HTTP 500): what it means and what to do. Source: https://noptcha.com/docs/errors/internal/ HTTP **500**, `application/problem+json`. When: our error. ## What to do An unexpected error on our side. Retry with backoff; if it persists, contact support with the `request_id`. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # service_unavailable > Noptcha API error service_unavailable (HTTP 503): what it means and what to do. Source: https://noptcha.com/docs/errors/service_unavailable/ HTTP **503**, `application/problem+json`. When: no capacity or a dependency is down. ## What to do No solver could take the task right now, or a dependency is down. Nothing was created or charged. Retry after `Retry-After` seconds. See all codes on [Errors](https://noptcha.com/docs/errors.md). Include the `request_id` when you contact support. --- # Create a task > POST /createTask: Create a task. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-create-task/ POST https://api.noptcha.com/createTask Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatCreateTask`. Creates a task and returns its integer `taskId`; poll it with `/getTaskResult`. When the solver answers synchronously (image types on our own models), the response already has `status: "ready"`, `solution` and `cost`; polling it afterwards returns the same result. When such a synchronous attempt fails, the response is the task's error (for example `ERROR_CAPTCHA_UNSOLVABLE`) and no task ID is returned; nothing is charged. `softId` / `appId` (digits) are recorded with the task for the developer programme. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | | `appId` optional | `integer | string` | Same as `softId`. | | `callbackUrl` optional | `string` | Where to POST the final result (the `getTaskResult` response plus `taskId`, see the `compatCallback` webhook). Same rules as v1 `callback_url`; an invalid URL is `ERROR_INCORRECT_SESSION_DATA`. Empty: no callback. max length 2,048 | | `softId` optional | `integer | string` | Developer programme ID (digits); recorded with the task. | | `task` required | `object` | The task in anti-captcha's format: `type` (see `CompatTaskType`) plus that type's fields (`websiteURL`, `websiteKey`, `body`, …; the field map per name is in `CompatTaskType`). Unknown fields are ignored. Parameters are validated against the canonical type's schema (see `CreateTaskRequest`). Proxy names (`…Task`, without `Proxyless`) need a proxy: `proxyType` (`http`, `https`, `socks4`, `socks5`), `proxyAddress`, `proxyPort`, optional `proxyLogin` and `proxyPassword`; or `proxy` as a URL string `http://user:pass@host:port`. Proxyless names ignore proxy fields. | | `task.proxyAddress` optional | `string` | | | `task.proxyLogin` optional | `string` | | | `task.proxyPassword` optional | `string` | | | `task.proxyPort` optional | `integer | string` | | | `task.proxyType` optional | `enum` | one of "http", "https", "socks4", "socks5" | | `task.type` required | `string` | Task type name in the compatibility API (case-insensitive). Each name maps to a canonical type and variant (see `CreateTaskRequest` for the parameters). Names ending in `Proxyless` ignore proxy fields; the others require a proxy. Unknown names: `ERROR_TASK_NOT_SUPPORTED`. `x-noptcha-aliases` lists, per name, `fields` (compatibility field → canonical parameter; `null`: accepted but not passed on; `to_array`: a single value is wrapped in an array) and `solution` (canonical solution field → compatibility field; `index_bools`: selected indexes become one boolean per image or tile). | Name | Canonical type | Variant | Notes | | --- | --- | --- | --- | | `FunCaptchaClassification` | `image.funcaptcha` | proxyless | | | `FunCaptchaTask` | `funcaptcha` | proxy | | | `FunCaptchaTaskProxyless` | `funcaptcha` | proxyless | | | `HCaptchaClassification` | `image.grid.hcaptcha` | proxyless | | | `HCaptchaTask` | `hcaptcha` | proxy | | | `HCaptchaTaskProxyless` | `hcaptcha` | proxyless | | | `ImageToTextTask` | `image.text` | proxyless | | | `NoCaptchaTask` | `recaptcha.v2` | proxy | | | `NoCaptchaTaskProxyless` | `recaptcha.v2` | proxyless | | | `ReCaptchaV2Classification` | `image.grid.recaptcha` | proxyless | | | `RecaptchaV2EnterpriseTask` | `recaptcha.v2.enterprise` | proxy | | | `RecaptchaV2EnterpriseTaskProxyless` | `recaptcha.v2.enterprise` | proxyless | | | `RecaptchaV2Task` | `recaptcha.v2` | proxy | | | `RecaptchaV2TaskProxyless` | `recaptcha.v2` | proxyless | | | `RecaptchaV3EnterpriseTask` | `recaptcha.v3.enterprise` | proxyless | | | `RecaptchaV3TaskProxyless` | `recaptcha.v3` | proxyless | `isEnterprise: true` → `recaptcha.v3.enterprise` | | `TurnstileTask` | `turnstile` | proxy | | | `TurnstileTaskProxyless` | `turnstile` | proxyless | | | Example ```json { "clientKey": "nop_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "task": { "type": "RecaptchaV2TaskProxyless", "websiteKey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", "websiteURL": "https://example.com/login" } } ``` ## Responses ### 200 Created (`errorId` 0) or rejected (`errorId` 1). Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatCreateTaskResult` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get the balance > POST /getBalance: Get the balance. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-get-balance/ POST https://api.noptcha.com/getBalance Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatGetBalance`. Available balance in US dollars (needs the `balance:read` scope). ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | ## Responses ### 200 Balance or an error. Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatBalance` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get a task's result > POST /getTaskResult: Get a task's result. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-get-task-result/ POST https://api.noptcha.com/getTaskResult Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatGetTaskResult`. Returns `status: "processing"` until the task completes, then `status: "ready"` with the solution, or the task's error (`errorId` 1). Results are kept for 10 minutes after completion; after that, and for tasks of other organizations, the answer is `ERROR_NO_SUCH_CAPCHA_ID`. Polling one task more than 5 times per second (bursts of 10) is answered with `processing` instead of an error, so clients keep polling. Likewise, when the API is momentarily at capacity, polling is answered with `processing` (the other compatibility endpoints answer `ERROR_NO_SLOT_AVAILABLE`; a rejected `/createTask` charges nothing and can be resent). Poll every 1–3 seconds. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | | `taskId` required | `one of` | The task ID, as a number or a string of digits. one of: `integer`, `string` | ## Responses ### 200 Processing, ready, or an error. Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatTaskProcessing` - `CompatTaskReady` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Report a correct token > POST /reportCorrectRecaptcha: Report a correct token. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-report-correct-recaptcha/ POST https://api.noptcha.com/reportCorrectRecaptcha Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatReportCorrectRecaptcha`. Reports whether a solved task's answer worked. Accepted for solved tasks within 10 minutes after completion; only the first report per task counts, later ones still succeed. Reports are recorded as a quality signal; they do not refund the task. Unsolved or unknown tasks: `ERROR_NO_SUCH_CAPCHA_ID`. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | | `taskId` required | `one of` | The task ID, as a number or a string of digits. one of: `integer`, `string` | ## Responses ### 200 Recorded (`errorId` 0) or an error. Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatReportResult` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Report a wrong image answer > POST /reportIncorrectImageCaptcha: Report a wrong image answer. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-report-incorrect-image-captcha/ POST https://api.noptcha.com/reportIncorrectImageCaptcha Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatReportIncorrectImageCaptcha`. Reports whether a solved task's answer worked. Accepted for solved tasks within 10 minutes after completion; only the first report per task counts, later ones still succeed. Reports are recorded as a quality signal; they do not refund the task. Unsolved or unknown tasks: `ERROR_NO_SUCH_CAPCHA_ID`. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | | `taskId` required | `one of` | The task ID, as a number or a string of digits. one of: `integer`, `string` | ## Responses ### 200 Recorded (`errorId` 0) or an error. Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatReportResult` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Report a wrong token > POST /reportIncorrectRecaptcha: Report a wrong token. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/compat-report-incorrect-recaptcha/ POST https://api.noptcha.com/reportIncorrectRecaptcha Compatibility API (anti-captcha compatible). [Guide](https://noptcha.com/docs/compatibility-api.md). Operation ID `compatReportIncorrectRecaptcha`. Reports whether a solved task's answer worked. Accepted for solved tasks within 10 minutes after completion; only the first report per task counts, later ones still succeed. Reports are recorded as a quality signal; they do not refund the task. Unsolved or unknown tasks: `ERROR_NO_SUCH_CAPCHA_ID`. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `clientKey` required | `string` | Your key (`nop_live_…` or `nop_test_…`). | | `taskId` required | `one of` | The task ID, as a number or a string of digits. one of: `integer`, `string` | ## Responses ### 200 Recorded (`errorId` 0) or an error. Headers: `Noptcha-Request-Id`. Content: `application/json` One of: - `CompatReportResult` - `CompatError` — A request or task error. `errorCode` values (anti-captcha's codes; new ones may be added): ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | **About the compatibility API:** The anti-captcha compatible JSON protocol. - Requests are `POST` with a JSON body. `Content-Type` is not required; the body is always parsed as JSON. - The key goes in `clientKey`. The key is checked before anything else, so an invalid key is reported even when other fields are wrong. - Responses are always HTTP 200. `errorId` is 0 on success and 1 on error; then `errorCode` and `errorDescription` say what went wrong (see `CompatError` for every code). - Two responses come from the infrastructure, not from the protocol: `403` (problem details, code `forbidden`) when a request reaches the API without passing through our edge network, and `500` (code `internal`) on an unexpected server error. Treat both as transient errors. - An unknown path, or a known path with another method (for example `GET /createTask`), is `404` (problem details, code `not_found`). - Task type names are the common anti-captcha compatible names (case-insensitive); each maps to a canonical type and variant, see `CompatTaskType`. - `callbackUrl` (optional) receives the final result as a POST (see Webhooks and the `compatCallback` webhook). An invalid URL is `ERROR_INCORRECT_SESSION_DATA` and no task is created. Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Create a task > POST /v1/tasks: Create a task. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/create-task/ POST https://api.noptcha.com/v1/tasks Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `createTask`. Creates a task. A task that is already final (solved or failed) is returned with **200**; a task that is still processing with **202**. Both carry `Location: /v1/tasks/{id}`. **Synchronous waiting.** With `Prefer: wait=N` (or `?wait=N`), the request waits up to N seconds (at most 90) for the result: 200 if the task completes in time, otherwise 202 and you poll `GET /v1/tasks/{id}`. `Preference-Applied: wait=N` confirms the wait (N capped at 90). Types solved synchronously return the result at once anyway. When an organization already has too many waiting requests, the request returns 202 without waiting and without `Preference-Applied`. **Idempotency.** With `Idempotency-Key` (1–64 visible ASCII characters, kept for 24 hours per organization), retrying the same request returns the same task instead of creating and charging a new one; replayed responses carry `Idempotent-Replayed: true`. The same key with a body that is not byte-for-byte identical (serialize once, resend the same bytes) is `409 idempotency_conflict`; so is a retry while the first request with that key is still being admitted (retry shortly). When the first request did not create a task (insufficient balance, no capacity, invalid parameters…) the key is released and can be reused. After the task's result has expired (10 minutes after completion) a replay is `404 not_found`: the task is not created again, use a new key. **Failures.** A task that cannot be solved is a successful request: 200 with `status: "failed"`, `error.code` and zero cost. `503 service_unavailable` means no solver could take the task right now; nothing was created or charged. **Callbacks.** With `callback_url`, the final result is also POSTed there as a `task.solved` or `task.failed` event (see Webhooks). An invalid URL is `422 invalid_params` with pointer `/callback_url` and no task is created; a `callback_url` that is not a string is `400 invalid_request`. `null` and `""` mean no callback. Every response passes through the per-key create limit and carries `RateLimit-Policy` and `RateLimit` (when rate limiting is enabled), including errors after authentication. ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). | Parameter | In | Description | | --- | --- | --- | | `Prefer` optional | header | `wait=N`: wait up to N seconds (capped at 90) for the result, RFC 7240. string · e.g. wait=60 | | `wait` optional | query | Seconds to wait for the result (capped at 90). Takes precedence over `Prefer: wait`. Values that are not a non-negative integer are ignored. integer · ≥ 0 | | `Idempotency-Key` optional | header | 1–64 visible ASCII characters (`!` to `~`, no spaces), unique per logical request; kept for 24 hours. Anything else is `400 invalid_request`. string · pattern: ^\[!-~\]{1,64}$ · e.g. 3f6c2b9e-8d41-4a7f-b0c5-1e2d3a4b5c6d | Body (`application/json`, required): One of (selected by `type`): - [`CreateCloudflareChallengeTaskRequest`](https://noptcha.com/docs/task-types/cloudflare-challenge.md) — Cloudflare challenge page (`cloudflare.challenge`, family `cloudflare`, active). Variants: `proxy` (`params.proxy` is required). Tasks not solved within 120 seconds fail with `timeout`. - [`CreateFuncaptchaTaskRequest`](https://noptcha.com/docs/task-types/funcaptcha.md) — FunCaptcha (Arkose Labs) (`funcaptcha`, family `arkose`, beta). Variants: `proxyless`, `proxy` (the `proxy` variant when `params.proxy` is given). Tasks not solved within 180 seconds fail with `timeout`. - [`CreateHcaptchaTaskRequest`](https://noptcha.com/docs/task-types/hcaptcha.md) — hCaptcha (`hcaptcha`, family `hcaptcha`, active). Variants: `proxyless`, `proxy` (the `proxy` variant when `params.proxy` is given). Tasks not solved within 120 seconds fail with `timeout`. - [`CreateImageFuncaptchaTaskRequest`](https://noptcha.com/docs/task-types/image-funcaptcha.md) — FunCaptcha image (`image.funcaptcha`, family `image`, beta). Variants: `proxyless`. Tasks not solved within 60 seconds fail with `timeout`. - [`CreateImageGridAwsTaskRequest`](https://noptcha.com/docs/task-types/image-grid-aws.md) — AWS WAF image grid (`image.grid.aws`, family `image`, beta). Variants: `proxyless`. Tasks not solved within 60 seconds fail with `timeout`. - [`CreateImageGridHcaptchaTaskRequest`](https://noptcha.com/docs/task-types/image-grid-hcaptcha.md) — hCaptcha image grid (`image.grid.hcaptcha`, family `image`, active). Variants: `proxyless`. Tasks not solved within 60 seconds fail with `timeout`. - [`CreateImageGridRecaptchaTaskRequest`](https://noptcha.com/docs/task-types/image-grid-recaptcha.md) — reCAPTCHA image grid (`image.grid.recaptcha`, family `image`, active). Variants: `proxyless`. Tasks not solved within 60 seconds fail with `timeout`. - [`CreateImageTextTaskRequest`](https://noptcha.com/docs/task-types/image-to-text.md) — Image to text (`image.text`, family `image`, active). Variants: `proxyless`. Tasks not solved within 60 seconds fail with `timeout`. - [`CreateRecaptchaV2TaskRequest`](https://noptcha.com/docs/task-types/recaptcha-v2.md) — reCAPTCHA v2 (`recaptcha.v2`, family `recaptcha`, active). Variants: `proxyless`, `proxy` (the `proxy` variant when `params.proxy` is given). Tasks not solved within 120 seconds fail with `timeout`. - [`CreateRecaptchaV2EnterpriseTaskRequest`](https://noptcha.com/docs/task-types/recaptcha-v2-enterprise.md) — reCAPTCHA v2 Enterprise (`recaptcha.v2.enterprise`, family `recaptcha`, active). Variants: `proxyless`, `proxy` (the `proxy` variant when `params.proxy` is given). Tasks not solved within 120 seconds fail with `timeout`. - [`CreateRecaptchaV3TaskRequest`](https://noptcha.com/docs/task-types/recaptcha-v3.md) — reCAPTCHA v3 (`recaptcha.v3`, family `recaptcha`, active). Variants: `proxyless`. Tasks not solved within 120 seconds fail with `timeout`. - [`CreateRecaptchaV3EnterpriseTaskRequest`](https://noptcha.com/docs/task-types/recaptcha-v3-enterprise.md) — reCAPTCHA v3 Enterprise (`recaptcha.v3.enterprise`, family `recaptcha`, active). Variants: `proxyless`. Tasks not solved within 120 seconds fail with `timeout`. - [`CreateTurnstileTaskRequest`](https://noptcha.com/docs/task-types/turnstile.md) — Cloudflare Turnstile (`turnstile`, family `cloudflare`, active). Variants: `proxyless`, `proxy` (the `proxy` variant when `params.proxy` is given). Tasks not solved within 120 seconds fail with `timeout`. Example ```json { "metadata": { "order": "A-17" }, "params": { "website_key": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", "website_url": "https://example.com/login" }, "type": "recaptcha.v2" } ``` ## Responses ### 200 The task is final (solved or failed). Headers: `Idempotent-Replayed` (optional), `Location` (optional), `Noptcha-Request-Id`, `Preference-Applied` (optional), `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `completed_at` optional | `string` | When the task became final; absent while processing. format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `cost` required | `object` | What the task cost (zero unless solved and charged). | | `cost.amount` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `cost.currency` required | `const "USD"` | | | `created_at` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `error` required | `one of | null` | Why the task failed when `status` is `failed`, else `null`. one of: `TaskError` | | `id` required | `string` | Task ID (a 53-bit integer as a string). pattern: ^\[0-9\]+$ | | `metadata` optional | `object` | Your own key-value pairs, returned with the task. At most 16 keys; keys 1–64 characters, values strings of at most 256 characters (Unicode code points, as JSON Schema counts them). A value that is not a string is `422 invalid_params` with pointer `/metadata/`. max 16 keys | | `metadata.` optional | `string` | max length 256 | | `params` required | `object | null` | The task's parameters with secrets removed (proxy login and password, cookies where the type marks them sensitive) and strings longer than 256 bytes (images) replaced by `"[N bytes]"`. `null` when not available. | | `sandbox` optional | `const true` | Present (true) on tasks created with a test key. | | `solution` required | `one of | null` | The answer when `status` is `solved`, else `null`. Its shape depends on `type`. one of: `Solution` | | `status` required | `enum` | one of "processing", "solved", "failed" | | `type` required | `string` | Canonical task type, e.g. `recaptcha.v2` (see `CreateTaskRequest`). | ### 202 The task is still processing; poll `Location`. Headers: `Idempotent-Replayed` (optional), `Location` (optional), `Noptcha-Request-Id`, `Preference-Applied` (optional), `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `completed_at` optional | `string` | When the task became final; absent while processing. format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `cost` required | `object` | What the task cost (zero unless solved and charged). | | `cost.amount` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `cost.currency` required | `const "USD"` | | | `created_at` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `error` required | `one of | null` | Why the task failed when `status` is `failed`, else `null`. one of: `TaskError` | | `id` required | `string` | Task ID (a 53-bit integer as a string). pattern: ^\[0-9\]+$ | | `metadata` optional | `object` | Your own key-value pairs, returned with the task. At most 16 keys; keys 1–64 characters, values strings of at most 256 characters (Unicode code points, as JSON Schema counts them). A value that is not a string is `422 invalid_params` with pointer `/metadata/`. max 16 keys | | `metadata.` optional | `string` | max length 256 | | `params` required | `object | null` | The task's parameters with secrets removed (proxy login and password, cookies where the type marks them sensitive) and strings longer than 256 bytes (images) replaced by `"[N bytes]"`. `null` when not available. | | `sandbox` optional | `const true` | Present (true) on tasks created with a test key. | | `solution` required | `one of | null` | The answer when `status` is `solved`, else `null`. Its shape depends on `type`. one of: `Solution` | | `status` required | `enum` | one of "processing", "solved", "failed" | | `type` required | `string` | Canonical task type, e.g. `recaptcha.v2` (see `CreateTaskRequest`). | ### 400 `invalid_request`: the body is not valid JSON or misses a field, a parameter is wrong, `callback_url` is not a string, the result would be too large (usage). `unsupported_type`: the task type does not exist or is not available. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 402 `insufficient_balance` or `budget_exceeded` (the key's spending budget for the period is used up). Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 404 `not_found`: no such task, it expired (results are kept 10 minutes after completion) or it belongs to another organization; or the task created with this `Idempotency-Key` has expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 409 `idempotency_conflict`: the `Idempotency-Key` was used with a different body, or the first request with it is still being processed (retry shortly). `task_not_solved`: feedback for a task that is not solved (still processing, or failed). Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 422 `invalid_params`: `params` or `metadata` do not match the schema; `errors` lists each problem. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | | `errors` required | `array of FieldError` | One entry per problem, sorted by `pointer`. At most 50 entries; when there are more, `detail` ends with "and N more errors". max items 50 | | `errors.[item]` optional | `object` | | | `errors.[item].message` required | `string` | | | `errors.[item].pointer` required | `string` | JSON Pointer (RFC 6901) to the offending value, from the root of the request body: `/params/website_key`, `/params/proxy/port`, `/params` for the parameters object itself, `/metadata/order`, `/metadata` for the metadata object itself. | | `errors.[item].reason` optional | `enum` | Machine-readable kind, present for image problems: `image_too_large` (over 1,000,000 characters, the pixel or frame limits, or the images of the task together over the per-task limit; the pointer is the image that went over), `image_empty`, `image_base64` (not valid base64), `image_format` (not a PNG, JPEG, GIF, WebP or BMP image, a multi-picture JPEG, or malformed or truncated). New values may be added. one of "image\_too\_large", "image\_empty", "image\_base64", "image\_format" | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get a task > GET /v1/tasks/{id}: Get a task. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/get-task/ GET https://api.noptcha.com/v1/tasks/{id} Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `getTask`. Returns the task: 200 when final, 202 while processing. With `?wait=N` (or `Prefer: wait=N`) this is a long poll of up to N seconds (at most 90) that returns as soon as the task completes. Unknown and expired tasks, tasks of other organizations, and sandbox tasks asked with a live key (or the reverse) are `404`. One task may be polled 5 times per second (bursts of 10); a long poll counts once. Over the limit: `429 rate_limited` with `Retry-After` and `RateLimit-Policy: "poll";q=10;w=2`. When the server already has too many waiting requests, a long poll returns `processing` at once (without `Preference-Applied`); wait a second before asking again. ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). | Parameter | In | Description | | --- | --- | --- | | `id` required | path | Task ID (decimal digits; the same number as the compatibility API's `taskId`). string · pattern: ^\[0-9\]{1,16}$ · e.g. 7259140378796032 | | `wait` optional | query | Seconds to wait for the result (capped at 90). Takes precedence over `Prefer: wait`. Values that are not a non-negative integer are ignored. integer · ≥ 0 | | `Prefer` optional | header | `wait=N`: wait up to N seconds (capped at 90) for the result, RFC 7240. string · e.g. wait=60 | ## Responses ### 200 The task is final. Headers: `Location` (optional), `Noptcha-Request-Id`, `Preference-Applied` (optional). Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `completed_at` optional | `string` | When the task became final; absent while processing. format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `cost` required | `object` | What the task cost (zero unless solved and charged). | | `cost.amount` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `cost.currency` required | `const "USD"` | | | `created_at` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `error` required | `one of | null` | Why the task failed when `status` is `failed`, else `null`. one of: `TaskError` | | `id` required | `string` | Task ID (a 53-bit integer as a string). pattern: ^\[0-9\]+$ | | `metadata` optional | `object` | Your own key-value pairs, returned with the task. At most 16 keys; keys 1–64 characters, values strings of at most 256 characters (Unicode code points, as JSON Schema counts them). A value that is not a string is `422 invalid_params` with pointer `/metadata/`. max 16 keys | | `metadata.` optional | `string` | max length 256 | | `params` required | `object | null` | The task's parameters with secrets removed (proxy login and password, cookies where the type marks them sensitive) and strings longer than 256 bytes (images) replaced by `"[N bytes]"`. `null` when not available. | | `sandbox` optional | `const true` | Present (true) on tasks created with a test key. | | `solution` required | `one of | null` | The answer when `status` is `solved`, else `null`. Its shape depends on `type`. one of: `Solution` | | `status` required | `enum` | one of "processing", "solved", "failed" | | `type` required | `string` | Canonical task type, e.g. `recaptcha.v2` (see `CreateTaskRequest`). | ### 202 The task is still processing. Headers: `Location` (optional), `Noptcha-Request-Id`, `Preference-Applied` (optional). Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `completed_at` optional | `string` | When the task became final; absent while processing. format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `cost` required | `object` | What the task cost (zero unless solved and charged). | | `cost.amount` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `cost.currency` required | `const "USD"` | | | `created_at` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `error` required | `one of | null` | Why the task failed when `status` is `failed`, else `null`. one of: `TaskError` | | `id` required | `string` | Task ID (a 53-bit integer as a string). pattern: ^\[0-9\]+$ | | `metadata` optional | `object` | Your own key-value pairs, returned with the task. At most 16 keys; keys 1–64 characters, values strings of at most 256 characters (Unicode code points, as JSON Schema counts them). A value that is not a string is `422 invalid_params` with pointer `/metadata/`. max 16 keys | | `metadata.` optional | `string` | max length 256 | | `params` required | `object | null` | The task's parameters with secrets removed (proxy login and password, cookies where the type marks them sensitive) and strings longer than 256 bytes (images) replaced by `"[N bytes]"`. `null` when not available. | | `sandbox` optional | `const true` | Present (true) on tasks created with a test key. | | `solution` required | `one of | null` | The answer when `status` is `solved`, else `null`. Its shape depends on `type`. one of: `Solution` | | `status` required | `enum` | one of "processing", "solved", "failed" | | `type` required | `string` | Canonical task type, e.g. `recaptcha.v2` (see `CreateTaskRequest`). | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 404 `not_found`: no such task, it expired (results are kept 10 minutes after completion) or it belongs to another organization; or the task created with this `Idempotency-Key` has expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Report whether an answer worked > POST /v1/tasks/{id}/feedback: Report whether an answer worked. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/send-task-feedback/ POST https://api.noptcha.com/v1/tasks/{id}/feedback Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `sendTaskFeedback`. Tells us whether a solved task's answer was accepted by the target site. Accepted for solved tasks within 10 minutes after completion; only the first report per task counts, later ones still return 200. Feedback is recorded as a quality signal; it does not refund the task. A task that is not solved (processing or failed) is `409 task_not_solved`. `reason` is optional and is not stored. ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). | Parameter | In | Description | | --- | --- | --- | | `id` required | path | Task ID (decimal digits; the same number as the compatibility API's `taskId`). string · pattern: ^\[0-9\]{1,16}$ · e.g. 7259140378796032 | Body (`application/json`, required): | Field | Type | Description | | --- | --- | --- | | `correct` required | `boolean` | Whether the answer was accepted by the target site. | | `reason` optional | `string` | Optional free text; not stored. | ## Responses ### 200 Recorded. Headers: `Noptcha-Request-Id`. Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `accepted` required | `const true` | | | `id` required | `string` | pattern: ^\[0-9\]+$ | ### 400 `invalid_request`: the body is not valid JSON or misses a field, a parameter is wrong, `callback_url` is not a string, the result would be too large (usage). `unsupported_type`: the task type does not exist or is not available. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 404 `not_found`: no such task, it expired (results are kept 10 minutes after completion) or it belongs to another organization; or the task created with this `Idempotency-Key` has expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 409 `idempotency_conflict`: the `Idempotency-Key` was used with a different body, or the first request with it is still being processed (retry shortly). `task_not_solved`: feedback for a task that is not solved (still processing, or failed). Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get the balance > GET /v1/balance: Get the balance. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/get-balance/ GET https://api.noptcha.com/v1/balance Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `getBalance`. The organization's balance (needs `balance:read`). ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). ## Responses ### 200 Balance. Headers: `Noptcha-Request-Id`. Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `available` required | `string` | What new tasks can use: cash + promo + credit limit − held. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `cash` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `credit_limit` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `currency` required | `const "USD"` | | | `held` required | `string` | Reserved for tasks in flight. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `promo` required | `string` | Promotional credit. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get the public catalog and list prices > GET /v1/catalog: Get the public catalog and list prices. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/get-catalog/ GET https://api.noptcha.com/v1/catalog Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `getCatalog`. The public task types with their list prices, for price tables and integrations that have no key yet. No authentication: the `Authorization` header is ignored (a valid, invalid or missing key gets the same response, and is not checked or counted against the IP). The items have the same shape as `GET /v1/types`, but list every public type at the list price, whatever an organization's actual prices or a key's allowed types; use `GET /v1/types` for the prices that apply to you. `placeholder` is `true` when the prices are not real prices (test deployments only); do not show them as prices then. Cacheable: `Cache-Control: public, max-age=300`, a strong `ETag`; send it back in `If-None-Match` to get `304 Not Modified`. `HEAD` works as well. Cross-origin reads are allowed from any origin: every response on this path, errors included, has `Access-Control-Allow-Origin: *` and exposes `ETag`, `Retry-After` and `Noptcha-Request-Id`. There is no CORS preflight (`OPTIONS` is not served), so from a browser send a simple request: a plain `fetch(url)`, or only CORS-safelisted headers such as `Accept`; a request with `Authorization` or another custom header is blocked by the browser. The SDKs' `get_catalog` / `getCatalog` send only `Accept` (no key, and the TypeScript SDK no `User-Agent`). Rate limit per client IP: 60 per minute, bursts of 30; for IPv6 per /64, and also 600 per minute, bursts of 300, per /48 (starting values). Over that `429 rate_limited` with `Retry-After`. ## Request Authentication: the key in the `clientKey` body field ([Keys](https://noptcha.com/docs/authentication.md)). | Parameter | In | Description | | --- | --- | --- | | `If-None-Match` optional | header | An `ETag` from an earlier response (or a list of them, or `*`); `304` when it still matches. string · e.g. "5d41402abc4b2a76b9719d911017c592" | ## Responses ### 200 The catalog. Headers: `Access-Control-Allow-Origin`, `Cache-Control`, `ETag`, `Noptcha-Request-Id`. Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `currency` required | `const "USD"` | | | `data` required | `array of TypeInfo` | Every public task type with a list price, ordered by `type`. | | `data.[item]` optional | `object` | | | `data.[item].family` required | `string` | Captcha family (concurrency is limited per family), e.g. `recaptcha`, `hcaptcha`, `cloudflare`, `arkose`, `image`. | | `data.[item].name` required | `string` | | | `data.[item].status` required | `string` | `active` or `beta`. | | `data.[item].timeout_seconds` required | `integer` | Deadline after which an unsolved task fails with `timeout`. | | `data.[item].type` required | `string` | | | `data.[item].variants` required | `array of object` | | | `data.[item].variants.[item]` optional | `object` | | | `data.[item].variants.[item].price` required | [`Money`](https://noptcha.com/docs/api-reference/schemas.md#Money) | | | `data.[item].variants.[item].variant` required | `string` | `proxyless` or `proxy` (the task has `params.proxy`). | | `placeholder` required | `boolean` | `true` when the prices are placeholders, not real prices (test deployments only). | | `price_version` required | `integer` | Version of the price list the prices come from. | ### 304 Not modified (`If-None-Match` matched the current `ETag`); no body. Headers: `Access-Control-Allow-Origin`, `Cache-Control`, `ETag`, `Noptcha-Request-Id`. ### 403 `forbidden` (problem details): the request reached the API without passing through our edge network. Infrastructure, not part of the compatibility protocol. Headers: `Noptcha-Request-Id`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Describe the current key > GET /v1/me: Describe the current key. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/get-me/ GET https://api.noptcha.com/v1/me Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `getMe`. The key's organization, settings, budget (with what is used in the current period) and the limits that apply to it. Any valid key may call this. The response never contains the key itself. ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). ## Responses ### 200 The key. Headers: `Noptcha-Request-Id`. Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `budget` required | `one of | null` | The key's spending budget; `null` when unlimited. one of: `object` | | `key` required | `object` | | | `key.allow_cidrs` required | `array | null` | Client IP ranges this key may be used from; `null` = any. | | `key.allowed_types` required | `array | null` | Task types this key may use; `null` = all. | | `key.expires_at` required | `string | null` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `key.id` required | `string` | | | `key.last4` required | `string` | | | `key.mode` required | `enum` | one of "live", "test" | | `key.name` required | `string` | | | `key.prefix` required | `string` | | | `key.revoke_at` required | `string | null` | When a rotated key stops working. format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `key.scopes` required | `array of string` | | | `limits` required | `object` | | | `limits.concurrency_by_family` required | `object` | Tasks in flight allowed for this organization, per captcha family (`recaptcha`, `hcaptcha`, …). Every public family is listed. Creating a task beyond the limit returns 429 `concurrency_limited`. Contact support to raise it. | | `limits.concurrency_by_family.` optional | `integer` | | | `limits.concurrency_per_family` required | `integer` | Default number of tasks in flight per captcha family. Your organization may have a different limit for some families; `concurrency_by_family` has the limit that actually applies. | | `limits.create_tasks` required | `one of | null` | Tasks this key may create per window; `null` when disabled. one of: `RatePolicy` | | `limits.sandbox` optional | `object` | Present for test keys. | | `limits.sandbox.create_tasks_per_org` required | `one of | null` | one of: `RatePolicy` | | `limits.sandbox.in_flight_per_org` required | `integer` | | | `limits.sandbox.waits_per_org_per_process` required | `integer` | | | `limits.task_polls` required | `one of | null` | Polls per task per window (including bursts); `null` when disabled. one of: `RatePolicy` | | `limits.wait_max_seconds` required | `integer` | Longest synchronous wait. | | `organization` required | `object` | | | `organization.id` required | `string` | | | `organization.name` required | `string` | | | `organization.status` required | `string` | | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # List task types and prices > GET /v1/types: List task types and prices. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/list-types/ GET https://api.noptcha.com/v1/types Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `listTypes`. The public task types this key may use, with the organization's actual price per variant. Types without a price are left out; `data` is an empty array when none is left. Any valid key may call this, whatever its scopes. ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). ## Responses ### 200 Types and prices. Headers: `Noptcha-Request-Id`. Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `data` required | `array of TypeInfo` | | | `data.[item]` optional | `object` | | | `data.[item].family` required | `string` | Captcha family (concurrency is limited per family), e.g. `recaptcha`, `hcaptcha`, `cloudflare`, `arkose`, `image`. | | `data.[item].name` required | `string` | | | `data.[item].status` required | `string` | `active` or `beta`. | | `data.[item].timeout_seconds` required | `integer` | Deadline after which an unsolved task fails with `timeout`. | | `data.[item].type` required | `string` | | | `data.[item].variants` required | `array of object` | | | `data.[item].variants.[item]` optional | `object` | | | `data.[item].variants.[item].price` required | [`Money`](https://noptcha.com/docs/api-reference/schemas.md#Money) | | | `data.[item].variants.[item].variant` required | `string` | `proxyless` or `proxy` (the task has `params.proxy`). | | `price_version` required | `integer` | Version of the price list the prices come from. | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Get usage > GET /v1/usage: Get usage. Noptcha API reference. Source: https://noptcha.com/docs/api-reference/get-usage/ GET https://api.noptcha.com/v1/usage Native API (v1). [Guide](https://noptcha.com/docs/native-api.md). Operation ID `getUsage`. Task counts and amounts per hour or day, by task type or by key (needs `usage:read`). Amounts are what was actually taken from the balance, after corrections. Only periods with data are returned (no zero rows), ordered by time and then type or key; the rows always add up to `totals`. `from` is rounded down and `to` up to period boundaries (`to` is exclusive). Without `to`: the end of the current period; without `from`: 24 hours (hourly) or 30 days (daily) before `to`. Ranges: hourly at most 7 days, daily at most 92 days; `from` not before 2026-01-01 and not in the future. `group_by=key` covers the last 90 days only: an earlier `from` is moved to the first available day (the response shows the effective `from`), a range entirely before that is `400`. Results over 10,000 rows (or over the query's resource limits) are `400` instead of being truncated; narrow the range or use `granularity=day`. Each parameter may be given once. Rate limit per organization: 20 per minute, bursts of 5 (test keys counted separately), with `RateLimit-Policy: "usage";q=20;w=60`. `503` when usage is temporarily unavailable (or only the per-key breakdown is, then with `Retry-After: 30`; `group_by=type` still works) and when too many usage queries are running (`Retry-After: 5`). Test keys get an empty report with `"sandbox": true` (parameters are still checked and rate limited). ## Request Authentication: `Authorization: Bearer ` ([Keys](https://noptcha.com/docs/authentication.md)). | Parameter | In | Description | | --- | --- | --- | | `from` optional | query | Start, RFC 3339 or `YYYY-MM-DD` (UTC). string · e.g. 2026-11-01 | | `to` optional | query | End (exclusive), RFC 3339 or `YYYY-MM-DD` (UTC). string | | `granularity` optional | query | string | | `group_by` optional | query | string | ## Responses ### 200 Usage. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/json` | Field | Type | Description | | --- | --- | --- | | `currency` required | `const "USD"` | | | `data` required | `array of UsageRow` | | | `data.[item]` optional | `object` | One period and type (`group_by=type`) or key (`group_by=key`). | | `data.[item].amount` required | `string` | cash + promo. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `data.[item].billed` required | `integer` | Tasks charged. | | `data.[item].cash` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `data.[item].promo` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `data.[item].unbilled` required | `integer` | Tasks not charged (unsolvable, timed out, cancelled). | | `data.[item].end` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `data.[item].key` optional | `one of | null` | With `group_by=key`. `null` is the unattributed remainder (the difference between the ledger and the per-key task log; usually zero, briefly non-zero while events are being processed). one of: `object` | | `data.[item].start` required | `string` | format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `data.[item].type` optional | `string` | With `group_by=type`. | | `data.[item].variant` optional | `string` | With `group_by=type`. | | `from` required | `string` | Effective start (after rounding and the 90-day limit of `group_by=key`). format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `granularity` required | `enum` | one of "hour", "day" | | `group_by` required | `enum` | one of "type", "key" | | `object` required | `const "usage"` | | | `sandbox` required | `boolean` | True for test keys (always an empty report). | | `to` required | `string` | Effective end (exclusive). format: date-time · pattern: ^\[0-9\]{4}-\[0-9\]{2}-\[0-9\]{2}T\[0-9\]{2}:\[0-9\]{2}:\[0-9\]{2}\\.\[0-9\]{3}Z$ | | `totals` required | `object` | | | `totals.amount` required | `string` | cash + promo. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `totals.billed` required | `integer` | Tasks charged. | | `totals.cash` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `totals.promo` required | `string` | Decimal US dollars with 6 places. pattern: ^-?\[0-9\]+\\.\[0-9\]{6}$ | | `totals.unbilled` required | `integer` | Tasks not charged (unsolvable, timed out, cancelled). | ### 400 `invalid_request`: the body is not valid JSON or misses a field, a parameter is wrong, `callback_url` is not a string, the result would be too large (usage). `unsupported_type`: the task type does not exist or is not available. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 401 `unauthorized`: the key is malformed, unknown, revoked or expired. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 403 `forbidden`: the key lacks the scope this endpoint needs, the client IP is not in the key's allowlist, the key may not use this task type, or the organization is suspended (`detail` says which). Also returned, without `detail`, when the request did not pass through our edge network. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 413 `request_too_large`: the body exceeds the limit (1 MB for image types, 64 KB otherwise). Any request with a body over 1 MB gets this, GET requests included. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 429 `rate_limited`: too many tasks created per second by this key, the same task polled too often, the sandbox's per-organization rate, usage queried too often, the catalog fetched too often from this IP, or this IP sent too many invalid keys. `concurrency_limited`: the organization has too many tasks in flight for this captcha family (or, with a test key, too many sandbox tasks still processing). Wait `Retry-After` seconds. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 500 `internal`: an error on our side. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional). Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | ### 503 `service_unavailable`: no capacity right now (all solvers busy or failing, the gateway is overloaded, a dependency is unavailable). For `POST /v1/tasks` nothing was created or charged. Wait `Retry-After`. Headers: `Noptcha-Request-Id`, `RateLimit` (optional), `RateLimit-Policy` (optional), `Retry-After`. Content: `application/problem+json` | Field | Type | Description | | --- | --- | --- | | `code` required | `string` | Machine-readable error code (see the table). | | `detail` optional | `string` | Human-readable explanation of this occurrence. | | `request_id` required | `string` | Same as the `Noptcha-Request-Id` header. | | `status` required | `integer` | The HTTP status code. | | `title` required | `string` | | | `type` required | `string` | format: uri | Header descriptions and every schema: [Schemas](https://noptcha.com/docs/api-reference/schemas.md). Machine-readable: [openapi.json](https://noptcha.com/openapi.json), [openapi.yaml](https://noptcha.com/openapi.yaml). --- # Noptcha > A captcha solving API with an anti-captcha compatible protocol and a native REST API. Free sandbox keys for integration. Source: https://noptcha.com/ Compatibility API The anti-captcha compatible JSON protocol: `createTask`, `getTaskResult`, `getBalance`. Change the host to `api.noptcha.com` and the key, and existing software keeps working. [Read more](https://noptcha.com/docs/compatibility-api.md) Native API (v1) REST + JSON: wait for the result in one request (`Prefer: wait`), retry safely with `Idempotency-Key`, errors as RFC 9457 problem details, usage reports. [Read more](https://noptcha.com/docs/native-api.md) Test keys `nop_test_` keys run in a sandbox: no charges, fixed answers, and magic values that simulate failures, timeouts and slow tasks. [Read more](https://noptcha.com/docs/sandbox.md) Task types reCAPTCHA v2 and v3 (including Enterprise), hCaptcha, Cloudflare Turnstile and challenge pages, FunCaptcha and image recognition. [See all types](https://noptcha.com/docs/task-types.md) ## Pay only for solved tasks A task that cannot be solved, times out or is rejected costs nothing. Prices are per task in US dollars; see [Pricing](https://noptcha.com/pricing.md). ## Start here [Quickstart](https://noptcha.com/docs/quickstart.md)Solve a first task with a test key in a few minutes. [API reference](https://noptcha.com/docs/api-reference.md)Every endpoint, generated from our OpenAPI description. [For AI agents](https://noptcha.com/llms.txt)The whole documentation as plain text. --- # Pricing > How Noptcha charges for captcha tasks. Prices per task type are published before launch. Source: https://noptcha.com/pricing/ ## How you are charged - **Per solved task.** A task is charged only when it is solved. Tasks that fail, time out or are rejected are not charged. - **US dollars, prepaid by default.** You top up your balance and each solved task is deducted from it. A credit limit is available only by separate agreement with us; by default it is zero. The price of each type applies to your organization; `GET /v1/types` returns the prices that apply to you, and every solved task carries its `cost`. - **Test keys are free.** Tasks created with a `nop_test_` key run in the [sandbox](https://noptcha.com/docs/sandbox.md) and are never charged. - **Budgets per key.** You can cap how much a key may spend per period ([Keys](https://noptcha.com/docs/authentication.md)). ## Prices The price list per task type will be published here before launch. ## Contact us Contact details for sales and volume pricing will be published here before launch. --- # Terms of Service > Draft terms for using Noptcha. Not yet in effect and not yet reviewed by counsel. Source: https://noptcha.com/legal/terms/ > **Draft — not yet in effect:** This is a working draft. It has not been reviewed by a lawyer, it is not in effect, and it may change completely before launch. Text marked \[TBD: …\] has not been decided. **Last updated:** \[TBD: date\] · **Effective:** \[TBD: effective date\] These Terms of Service (“Terms”) are an agreement between you and Noptcha, LLC, a \[TBD: state of organization\] limited liability company (“Noptcha”, “we”, “us”), with its address at \[TBD: company address\]. They cover the Noptcha API at `api.noptcha.com`, the console at `app.noptcha.com`, this website and our SDKs (together, the “Service”). These Terms include the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md) and the [Refund Policy](https://noptcha.com/legal/refunds.md). The [Privacy Policy](https://noptcha.com/legal/privacy.md) explains how we handle personal data. ## 1\. Accepting these Terms By creating an account, or by using an API key, you accept these Terms. If you accept them on behalf of a company or other organization, you confirm that you are authorized to bind it, and “you” means that organization. - **Who may use the Service.** The Service is intended for \[TBD: businesses and developers only, or also consumers\]. You must be at least \[TBD: minimum age\] years old. - **Restricted countries and persons.** You may not use the Service if you are located in, or organized or ordinarily resident in, \[TBD: list of sanctioned or restricted countries and regions\], or if you are a person with whom we are prohibited from dealing under applicable sanctions laws. We may block access from these places. ## 2\. Accounts and organizations - **Sign-up.** You sign up with an email address and confirm it through a link we send; your account is created only after that. Each account belongs to a person. An organization is created for you when you sign up. - **Two-step verification is mandatory.** Every sign-in requires a second step (an authenticator app or a one-time code by email). Keep your password, authenticator and recovery codes safe. Some sensitive actions ask you to verify again. - **Organizations and roles.** Balance, keys, prices and limits belong to an organization. Members have a role (owner, admin, developer, billing or viewer). Every organization has at least one owner. The organization is responsible for what its members do in the Service. - **Accurate information.** Give us accurate information and keep your email address working: we use it for security notices, payment notices and notices under these Terms. - **One person, one account.** Do not share an account between people; invite them as members instead. Do not create accounts to get around a suspension, a limit or a promotion’s terms. ## 3\. API keys - A key (`nop_live_…` or `nop_test_…`) is shown **only once**, when it is created. We store only a hash of it and cannot show it again; if you lose it, create a new one or rotate it. - **You are responsible for all use of your keys**, including charges for tasks created with them, until you revoke them. Keep keys out of public code and client-side applications. If a key leaks, revoke or rotate it immediately. - You can limit a key by scope, task type, IP address range, spending budget and expiry date. These limits are tools to help you; setting them is your choice. - **Test keys** (`nop_test_…`) run in a sandbox: they return fixed answers, never contact a solving resource and are never charged. See [Sandbox](https://noptcha.com/docs/sandbox.md). ## 4\. The Service - **What we do.** You submit a captcha task; we route it to a solving resource, which may be our own recognition software, a third-party solving provider or an AI model, and return the result. We choose the route; you cannot choose a specific provider. - **No guarantee of a result.** A task may fail or time out. Each task type has a time limit (for example, 120 seconds for most token types). A solved result is a best effort: we do not guarantee that a token or answer will be accepted by the website that issued the captcha. - **Results are kept briefly.** The result of a task can be fetched for 10 minutes after the task finishes; after that it is gone. - **Limits.** Requests are subject to rate limits, concurrency limits and request size limits, described in [Rate limits and quotas](https://noptcha.com/docs/limits.md). We may change them; for an organization we may also lower them to protect the Service. - **Changes.** We may add, change or remove task types, features and API versions. We follow the policy in [Versioning and compatibility](https://noptcha.com/docs/versioning.md) for breaking changes to the API. - **Availability.** We work to keep the Service available, but we do not promise uninterrupted service. \[TBD: whether to offer a service level (SLA) and service credits\] ## 5\. Prices, balance and payment - **Prepaid, in US dollars.** You add funds to your organization’s balance (“top up”) and solved tasks are deducted from it. All amounts are in US dollars. - **You pay only for solved tasks.** A task is charged only when it is solved. Tasks that fail, time out or are rejected are not charged. When you create a task, its price is reserved from your available balance; the reserved amount is released if the task is not solved. - **Prices.** The price of each task type is shown in the console and returned by `GET /v1/types`. The price is fixed when the task is created, so a price change never affects a task already in progress. We may change prices for new tasks \[TBD: with how much notice, and how we notify you\]. - **Top-ups.** A single top-up must currently be between **$5 and $10,000**. You must confirm your email address before you can top up. Payments are processed by third-party payment processors (card and wallet payments, and cryptocurrency); we do not receive or store your full card number. For cryptocurrency, the amount credited is the US dollar value actually received. - **Taxes.** \[TBD: whether we charge VAT, GST or sales tax, and how prices are stated with respect to tax\] - **Balance expiry.** Your cash balance (from top-ups) does not expire. \[TBD: confirm no expiry and no dormancy fee, or set an inactivity rule\] - **Promotional credit.** Credit from sign-up offers, promo codes or compensation from us is kept separately from cash. It is used before cash, it may have an expiry date (shown in the console), and it **cannot be refunded, withdrawn or transferred**. We may withdraw promotional credit that was issued by mistake or obtained in breach of these Terms or the promotion’s terms. - **Negative balance.** Your balance can fall below zero in some cases, for example when tasks already in progress are charged after promotional credit expires, or under a credit limit. Any negative balance is an amount you owe us. A credit limit exists only if we agree to it with you in writing; by default it is zero. - **Key budgets** limit what a key can spend; they do not limit your liability for charges within them. - **Receipts.** \[TBD: receipts and invoices: format, availability in the console, and the billing details shown\] ## 6\. Refunds and chargebacks Refunds of unused balance, promotional credit, refunds made through the payment processor and chargebacks are covered by the [Refund Policy](https://noptcha.com/legal/refunds.md). In short: unused cash balance can be refunded on request, promotional credit cannot, and a chargeback or a fraud warning from the payment processor suspends the organization while it is resolved. ## 7\. Acceptable use You must follow the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md). You are responsible for making sure that your use of the Service, and the use of the websites and systems you interact with through it, is lawful and authorized. ## 8\. Suspension and termination - **By us.** Enforcement is largely automated by rules. We may suspend (freeze) an organization, revoke keys or block requests, without prior notice, when we reasonably believe that the account breaches these Terms or the Acceptable Use Policy, when a payment is disputed or flagged as possibly fraudulent, when the balance is negative, or when we are required to by law. While an organization is frozen, its keys cannot create tasks and it cannot redeem promo codes or top up, except that an organization frozen only because its balance went negative can top up to clear it. - **Appeals.** If you believe a suspension is wrong, contact us at \[TBD: support or appeals contact\]. We will review the appeal and tell you the outcome \[TBD: target response time\]. - **Termination for serious breach.** We may close an account permanently for serious or repeated breaches. \[TBD: whether unused cash balance is refunded or forfeited when an account is closed for a breach\] - **By you.** You can stop using the Service at any time. An owner can close an organization in the console and reopen it within 30 days. If the organization still has cash balance, ask for a refund before closing (see the [Refund Policy](https://noptcha.com/legal/refunds.md)); to close it anyway you confirm that you give up that balance, and you can still ask for the refund within those 30 days by reopening the organization first. A payment that arrives after the organization was closed is not added to it: we add it if the organization is reopened within the 30 days, and otherwise (or once you tell us it will not be reopened) return the full amount to the payer (see the [Refund Policy](https://noptcha.com/legal/refunds.md)). You can also delete your account in the console. What happens to your data afterwards is described in the [Privacy Policy](https://noptcha.com/legal/privacy.md). - Sections that by their nature should survive termination (including payment obligations, disclaimers, limitation of liability and indemnity) survive it. ## 9\. Reporting incorrect answers You can report whether an answer was correct (`POST /v1/tasks/{id}/feedback` or the compatible report methods) within 10 minutes after a task finishes. We use reports to measure and improve quality. **Reports do not currently lead to a refund of the task.** \[TBD: whether and when per-task refunds based on reports will be offered\] ## 10\. Your data and task content - You keep your rights in the data you submit (for example images, website addresses, site keys, proxies). You give us permission to process it, and to send it to the solving resources that handle your tasks, only to provide, secure and improve the Service. - You confirm that you have the right to submit that data and that submitting it does not violate law or anyone else’s rights. If you submit proxy credentials, you confirm that you are authorized to use that proxy. - We handle personal data as described in the [Privacy Policy](https://noptcha.com/legal/privacy.md). \[TBD: whether a data processing agreement (DPA) is offered, and on what terms\] ## 11\. Our property The Service, this website and its documentation belong to us or our licensors. You may use the documentation and code examples to integrate with the Service. Our SDKs are licensed under \[TBD: SDK license\]. Do not copy, resell or offer the Service to others as your own service except as allowed by the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md). If you send us suggestions, we may use them without obligation. ## 12\. Disclaimer of warranties \[TBD: disclaimer text to be drafted by counsel, including any wording required for consumers\] To the extent permitted by law, the Service is provided “as is” and “as available”, without warranties of any kind, including merchantability, fitness for a particular purpose and non-infringement. ## 13\. Limitation of liability To the extent permitted by law, we are not liable for indirect, incidental, special, consequential or punitive damages, or for lost profits, revenue or data. Our total liability for all claims relating to the Service is limited to \[TBD: liability cap, for example the amounts you paid in a period before the claim\]. ## 14\. Indemnity You will defend and indemnify Noptcha against claims by third parties arising from your use of the Service in breach of these Terms or the law, including claims by the operators of websites you interact with through the Service. \[TBD: scope of indemnity, and whether it applies to consumers\] ## 15\. Changes to these Terms We may update these Terms. For material changes we will notify you by email or in the console \[TBD: notice period, for example 30 days\] before they take effect. If you continue to use the Service after the change takes effect, you accept the updated Terms. ## 16\. Governing law and disputes These Terms are governed by \[TBD: governing law\]. Disputes will be resolved by \[TBD: dispute resolution method: courts or arbitration, venue, class action waiver, small-claims option\]. ## 17\. Legal requests We respond to valid legal process from law enforcement and other authorities, as described in the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md#law-enforcement-and-legal-requests). ## 18\. General These Terms, with the policies they refer to, are the whole agreement between you and us about the Service. If a provision is unenforceable, the rest remains in effect. Not enforcing a provision is not a waiver. You may not assign these Terms without our consent; we may assign them in connection with a merger, acquisition or sale of assets. Neither party is liable for delays caused by events beyond its reasonable control. \[TBD: language of the binding version, if translations are published\] ## 19\. Contact Noptcha, LLC · \[TBD: company address\] · \[TBD: contact email for legal notices\] --- # Acceptable Use Policy > Draft policy on what Noptcha may not be used for and how violations are handled. Not yet in effect. Source: https://noptcha.com/legal/acceptable-use/ > **Draft — not yet in effect:** This is a working draft. It has not been reviewed by a lawyer, it is not in effect, and it may change completely before launch. Text marked \[TBD: …\] has not been decided. **Last updated:** \[TBD: date\] · **Effective:** \[TBD: effective date\] This Acceptable Use Policy (“AUP”) is part of the [Terms of Service](https://noptcha.com/legal/terms.md). It applies to everyone who uses Noptcha, including every member of an organization and everyone who uses its API keys. The organization is responsible for all use made with its keys. Captcha solving has legitimate uses: testing and monitoring your own websites, accessibility, research, quality assurance and automating access to services you are authorized to use. It can also be misused. You may use Noptcha only for lawful purposes and only to access websites, accounts and systems that you are authorized to access. ## Prohibited uses You may not use Noptcha, directly or through someone else, to: 1. **Gain unauthorized access.** Access accounts, systems or data without authorization; bypass access controls or security measures of systems you are not authorized to access. 2. **Take over accounts.** Credential stuffing, password spraying, brute-forcing logins or one-time codes, or any other attempt to take over accounts that are not yours. 3. **Commit fraud.** Payment fraud, card testing (“carding”), using stolen identities or payment details, creating accounts with false identities for fraud, phishing, or obtaining money, goods or services by deception. 4. **Manipulate counts and outcomes.** Fake votes, fake reviews, fake sign-ups or fake engagement; inflating views, clicks, followers or other metrics; ad fraud; automated purchase of limited inventory (such as tickets) in breach of law; or manipulating polls, contests, giveaways or elections. 5. **Send spam.** Send unsolicited bulk messages, post spam comments or forum content, or create accounts in bulk to send spam. 6. **Distribute illegal or harmful content.** Child sexual abuse material, content that infringes intellectual property, malware, or any other content that is illegal where it is published or received. 7. **Harm people.** Harass, stalk, threaten or intimidate anyone; collect personal data unlawfully; or dox people. 8. **Evade sanctions or export controls.** Use the Service from, for, or on behalf of sanctioned countries, regions or persons, or to help anyone evade sanctions or export controls. 9. **Abuse target websites.** Send traffic that overloads or disrupts a website or service (including denial of service), or collect data in violation of applicable law. 10. **Break the law.** Do anything else that violates applicable law or regulation, or that helps someone else do it. ## Abuse of Noptcha itself You may not: - attack, probe or overload the Service, try to get around rate limits, concurrency limits or key restrictions, or try to access other customers’ data or tasks; - use proxies, accounts or credentials you are not authorized to use in task parameters; - deliberately submit false reports about answers, or report answers in bulk without checking them; - create multiple accounts or organizations to abuse sign-up offers or promo codes, or to get around a suspension; - top up with payment methods you are not authorized to use, or file chargebacks for payments you made; - resell or offer the Service to others \[TBD: whether reselling, white-labeling or integrating Noptcha into a product sold to others is allowed, and on what conditions\]; - use test keys or the sandbox for load testing beyond its published limits. ## How we enforce this policy Enforcement is largely automated: rules detect patterns of abuse and act on them, and you can appeal. Depending on the case we may, without prior notice: - reject or rate-limit requests, or block an IP address or network; - disable or revoke an API key; - freeze an organization, so that it cannot create tasks, top up or redeem promo codes; - withdraw promotional credit obtained in breach of this policy; - close the account, as described in the [Terms of Service](https://noptcha.com/legal/terms.md#8-suspension-and-termination); - report the activity to the payment processor or to the authorities where required or appropriate. When we freeze an organization or close an account, the console shows that it is frozen. \[TBD: whether and how we notify the owners by email, and the exceptions (legal prohibition, ongoing investigation)\] **Appeals.** If you think we got it wrong, write to \[TBD: appeals contact\] from an email address of an owner of the organization, with the organization ID and an explanation of your use case. We may ask for more information. \[TBD: target response time for appeals\] ## Reporting abuse If you believe Noptcha is being used against your website or service in breach of this policy, contact \[TBD: abuse contact\] with as much detail as you can (times, the requests or traffic you saw, and what happened). We do not publish who our customers are; we review reports and act under this policy. ## Law enforcement and legal requests We cooperate with law enforcement and other authorities when we receive valid legal process (such as a subpoena, court order or warrant) that applies to us. We review each request, and we may refuse or narrow requests that are invalid or too broad. We may preserve data relevant to a request. \[TBD: whether and when we notify the affected customer about a request, and whether we publish a transparency report\] Requests should be sent to \[TBD: legal process contact\]. What data we hold, and for how long, is described in the [Privacy Policy](https://noptcha.com/legal/privacy.md). We may also disclose information without legal process when we believe in good faith that it is necessary to prevent imminent harm to a person, or to protect the Service, our customers or others from fraud or abuse. ## Changes We may update this policy. Material changes are announced as described in the [Terms of Service](https://noptcha.com/legal/terms.md#15-changes-to-these-terms). --- # Privacy Policy > Draft policy on what personal data Noptcha collects, why, how long it is kept and your rights. Not yet in effect. Source: https://noptcha.com/legal/privacy/ > **Draft — not yet in effect:** This is a working draft. It has not been reviewed by a lawyer, it is not in effect, and it may change completely before launch. Text marked \[TBD: …\] has not been decided. **Last updated:** \[TBD: date\] · **Effective:** \[TBD: effective date\] This policy explains how Noptcha, LLC (“Noptcha”, “we”) handles personal data when you use the Noptcha API, the console at `app.noptcha.com` and this website. We are the controller of the personal data described here. \[TBD: whether we act as a processor for personal data contained in task content, and whether a DPA is offered\] ## Summary - We collect what we need to run accounts, process tasks, take payments and keep the Service secure. - We do not sell personal data, we do not use advertising or analytics cookies, and we do not send marketing email \[TBD: confirm, or describe opt-in marketing\]. - Task content and answers are kept only briefly (minutes). Proxy usernames and passwords in tasks are never stored. - The IP address a task came from is kept for 30 days; after that only its network prefix is kept. ## What we collect ### Account and organization - **Account:** email address, name (if you give one), a hash of your password, your two-step verification settings (the authenticator secret is stored encrypted; recovery codes are stored as hashes), and the time of your last sign-in. - **Sessions and devices:** for each signed-in session, the IP address and a shortened browser description (user agent); for devices you have signed in from, a label such as “Chrome / macOS”, when it was first and last seen, and its last IP address. Device recognition uses a random identifier in a cookie; we store only a hash of it. - **Organization:** its name, members and their roles, invitations (the invited email address), country, billing email and, if you enter them, billing details (legal name, address, tax ID). - **API keys:** we store a hash of each key, its prefix and last four characters, its settings and when it was last used. We cannot recover a key from what we store. - **Security and audit log:** a record of security-relevant actions (sign-ins, key changes, payment actions and so on), with the IP address and browser description. ### Payments - Top-ups and refunds: amount, payment method type, status and the payment processor’s order and refund references. - Card and wallet payments are handled by the payment processor; **we do not receive or store your full card number**. - For cryptocurrency payments we keep the paying address reported by the processor, and for refunds the address and network you ask us to send the refund to. ### Tasks - **Task content:** what you send to solve a task, for example the website address and site key, an image, or proxy details. We pass task content to the solving resource that handles the task. - **Task records:** for each task, its type, outcome, timing, price, the website’s domain, the IP address the request came from (and its network prefix and country), and the key and organization that created it. - **Kept briefly, then gone:** task content is held in short-lived storage while the task is processed and for up to 30 minutes after the task’s deadline. Answers (including tokens) are kept for 10 minutes after the task finishes, so you can fetch them, and for up to 15 minutes in a webhook delivery that is still being retried. Neither is written to our databases or logs. Proxy usernames and passwords are never written to any database or log. \[TBD: if samples (images, requests) are ever kept for quality checks (designed as 3 days), add it here first\] - **Webhooks:** the endpoint addresses you configure, and a record of each delivery attempt (time, result, HTTP status, host of the endpoint). ### Support and communication Email we send you (security notices such as a password change or a new two-step verification setting, balance and payment notices) and anything you send us. \[TBD: support channel and what it records\] ### This website The documentation site does not use cookies or analytics. Our network provider may record your IP address and browser in its logs for security. \[TBD: confirm, and the retention of edge logs\] ## Why we use it | Purpose | Data | Legal basis \[TBD: confirm which laws apply (for example GDPR, UK GDPR, CCPA)\] | | --- | --- | --- | | Create and run your account and organization; process tasks | Account, organization, keys, task content and records | Performing our contract with you | | Take payments, issue refunds, keep financial records | Payments, billing details | Contract; legal obligations (tax and accounting) | | Keep accounts and the Service secure: two-step verification, device recognition, rate limits, blocking abuse, detecting fraud and chargebacks | Sessions, devices, IP addresses, audit log, task records | Legitimate interests (security and fraud prevention) | | Enforce the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md) and respond to legal requests | The data relevant to the case | Legitimate interests; legal obligations | | Measure and improve quality and routing | Task records, answer reports | Legitimate interests | | Send service, security and payment notices | Email address | Contract; legitimate interests | ## Who processes it We use service providers (“subprocessors”) that process personal data on our behalf, under contracts that limit their use of it. By category: | Category | What they process | | --- | --- | | Cloud hosting (servers in Singapore; backup copies in \[TBD: region\]) | All data we hold | | Network, DNS and security (content delivery, firewall, access protection) | IP addresses, request metadata | | Object storage (backups, documents) | Backups of our databases | | Email delivery | Email address, email content | | Payment processing (card and wallet payments; cryptocurrency payments; a payment gateway operated by \[TBD: an affiliated company — confirm how to describe it\]) | Payment details, billing email, amounts | | Human verification on sign-up and sign-in | IP address, browser signals | | Breached-password check | Only the first 5 characters of a hash of the password (it cannot be reversed to the password) | | Captcha solving resources: third-party solving providers, AI model providers and an affiliated recognition service | Task content (website address, site key, images, proxy details you supply) | \[TBD: whether to publish a named list of subprocessors (and where), and how customers are notified of changes\] We also disclose data when required by law or valid legal process (see the [Acceptable Use Policy](https://noptcha.com/legal/acceptable-use.md#law-enforcement-and-legal-requests)), to protect people, the Service or our customers from fraud or harm, and to a successor in a merger, acquisition or sale of assets. ## How long we keep it | Data | Kept for | | --- | --- | | Task content and answers | Task content up to 30 minutes after the task’s deadline; answers 10 minutes after the task finishes (up to 15 minutes in a webhook delivery being retried) | | Task records | 90 days; the source IP address is removed after 30 days and only its network prefix is kept | | Usage totals per organization, hour and task type (no personal data) | As long as the financial records | | Webhook delivery records | 7 days | | Security and audit log | \[TBD: 2 years (planned; automatic deletion is not built yet)\] | | Server logs | \[TBD: 90 days (planned)\] | | Backups | 30 days | | Payments, refunds and the ledger | \[TBD: at least 7 years, to be confirmed with our accountant\]; after account deletion they are kept with the organization ID only, without personal details | | Receipts and credit notes already issued | \[TBD: at least 7 years, to be confirmed with our accountant\]. They keep the billing details printed on them (billing name — or the organization name if no billing name was set — address, country, tax ID and billing email, plus the organization ID), also after account deletion, because financial records must be kept as issued. The billing details you saved in the console are deleted with the organization’s other personal data, 30 days after it is closed | | Account and organization data | Until the organization is closed or the account is deleted, then deleted 30 days later as described below | ## Your rights Depending on where you live, you may have the right to access, correct, delete or export your personal data, to object to or restrict some processing, and to complain to a data protection authority. - **Export:** every member can export their data as a file (JSON) from the console (Account → Export my data): their account, sign-in history and devices, and for each organization they belong to, the organization’s full data if they are an owner or admin (including its billing details and the receipts and credit notes issued to it, without the PDF files), or only the records about themselves otherwise. Activity of other people in the file does not include their IP addresses or browsers. Up to 5 exports a day; very large exports are done by support. - **Correction:** some details can be changed in the console (for example your password and two-step verification); for anything else, contact us. - **Closing an organization:** an owner can close an organization in the console. Its API keys are revoked and its webhooks turned off at once, and nobody can open it any more. An owner can reopen it within 30 days. After 30 days the personal data in the organization (members, webhook endpoints, key names, the billing details saved in the console, IP addresses and other personal fields in its activity log) is deleted; receipts and credit notes already issued are kept as described above. If something about the organization’s money is still open at that point (a payment dispute, a refund in progress, a negative balance, or a payment that arrived after closing), deletion waits until it is resolved. - **Deleting your account:** you can delete your account in the console (Account → Delete account), unless you are the only owner of an organization that is still open (add another owner or close it first). You are signed out everywhere and leave all organizations at once; this cannot be undone from the console. The owners of those organizations that are still open are told by email that you deleted your account, and invitations you sent are cancelled. API keys and webhook endpoints you created belong to the organization and stay there. 30 days later your personal data (email address, name, sign-in history, devices, IP addresses in our logs, invitations sent to your email address and that address in other organizations’ activity logs) is deleted. Until then the email address cannot be used to sign up again. \[TBD: whether support can restore a deleted account within the 30 days\] - Financial records (payments, refunds, the ledger) are kept for the period above, with the organization ID only; receipts and credit notes already issued keep the billing details printed on them for their retention period (see the table above). Backups expire within 30 days after deletion. \[TBD: whether payment processor notifications, refund payout addresses and our staff’s activity log are anonymized at the same time\] - **Other requests:** write to \[TBD: privacy contact\]. We may need to verify that the request comes from you. We answer within \[TBD: response time, for example 30 days\]. ## Cookies and local storage The console at `app.noptcha.com` uses only cookies that are needed for it to work: | Name | Purpose | Lifetime | | --- | --- | --- | | `__Host-nop_sid` | Your signed-in session (not readable by scripts) | Until you sign out or the session expires | | `__Host-nop_csrf` | Protects forms against cross-site request forgery | Until you close the browser | | `__Host-nop_dev` | Recognizes a device you signed in from before, so you are not asked for human verification again | 400 days; removed when you delete the device | The console and this website keep your light or dark theme choice in your browser’s local storage. Payment pages are run by the payment processor and the human verification check is run by its provider; they may set their own cookies. \[TBD: list them, or link to the providers’ policies\] ## International transfers We are a US company and our servers are in Singapore; some providers process data in other countries. When we transfer personal data from the EEA, the UK or Switzerland, we rely on \[TBD: transfer mechanism, for example Standard Contractual Clauses\]. ## Security Accounts require two-step verification; passwords are hashed with a modern algorithm; API keys are stored only as hashes; access to production systems is limited and logged. No system is perfectly secure; if a breach affects your personal data, we will notify you as required by law. ## Children The Service is not directed at children, and you must be at least \[TBD: minimum age\] years old to use it. ## Changes We will post changes here and update the date above. For material changes we will notify account owners by email \[TBD: notice period\] before they take effect. ## Contact Noptcha, LLC · \[TBD: company address\] · \[TBD: privacy contact email\] · \[TBD: EU/UK representative, if required\] --- # Refund Policy > Draft policy on refunds of unused balance, promotional credit, payment disputes and chargebacks. Not yet in effect. Source: https://noptcha.com/legal/refunds/ > **Draft — not yet in effect:** This is a working draft. It has not been reviewed by a lawyer, it is not in effect, and it may change completely before launch. Text marked \[TBD: …\] has not been decided. **Last updated:** \[TBD: date\] · **Effective:** \[TBD: effective date\] This policy is part of the [Terms of Service](https://noptcha.com/legal/terms.md). All amounts are in US dollars. ## Tasks that are not solved are not charged You are charged only for solved tasks. When a task fails, times out or is rejected, the amount reserved for it is released back to your available balance automatically. There is nothing to claim. ## Incorrect answers You can report an incorrect answer within 10 minutes after a task finishes. Reports help us measure quality and route tasks better, but **they do not currently refund the task**. \[TBD: whether per-task refunds based on reports will be offered, for which task types and with what limits\] ## Refunding unused balance You can ask for a refund of unused **cash** balance (the part of your balance that came from top-ups). - **How to ask.** An owner or billing member writes to \[TBD: billing contact\] with the organization ID and the amount. We refund against specific top-ups. \[TBD: whether the console will offer a refund request form\] - **Time limit.** \[TBD: how long after a top-up a refund can be requested, for example 30 or 90 days, or no limit\] - **How much.** At most the unused cash balance at the time of the refund, and for each top-up at most what was paid for it minus what has already been refunded from it. Promotional credit is not included. - **Fees.** \[TBD: whether payment processing fees are deducted from refunds\] - **Where the money goes.** - **Card and wallet payments** are refunded to the original payment method through the payment processor. We cannot send a card refund anywhere else. How long it takes to appear depends on your bank. - **Cryptocurrency payments** cannot be refunded automatically. We send them manually to an address and network you give us, and we may ask you to show that you control the address the payment came from. \[TBD: which asset is sent, how the amount is converted, and who pays the network fee\] - **Your balance goes down first.** The refunded amount is taken from your balance when we start the refund. If the refund fails, the amount is added back. - **Processing time.** \[TBD: target time to process a refund request\] ## Promotional credit Promotional credit (from sign-up offers, promo codes or compensation from us) is **not refundable** and cannot be withdrawn or transferred. It is used before cash and may expire on the date shown in the console. ## Closing your account Ask for the refund **before** you close your organization. To close an organization that still has cash balance, you confirm that you give it up; within 30 days of closing you can still reopen the organization and ask for the refund under the rules above. \[TBD: whether given-up balance can still be refunded after the 30 days, for example on request to support\] A payment that arrives after the organization was closed (for example a cryptocurrency payment that confirms late) is not added to the closed organization. If the organization is reopened within the 30 days, we add the payment to it. If it is not reopened within the 30 days, or you tell us it will not be reopened, we return the full amount to the payer: a card payment is refunded to the card, and a cryptocurrency payment is sent back to the address it was paid from. Once a payment has been returned, it is not added to the organization later, even if the organization is reopened. \[TBD: who bears the network fee for sending a cryptocurrency payment back\] \[TBD: whether unused cash balance is refunded when we close an account for a breach of the Terms or the Acceptable Use Policy\] ## Refunds made through the payment processor If a payment is refunded through the payment processor for any reason, the refunded amount is deducted from your cash balance (down to zero). ## Possible fraud If the payment processor warns that a payment may be fraudulent (for example a stolen card), we freeze the organization. \[TBD: confirm this default:\] if the payment has not been disputed yet and the balance covers it, we refund the payment in full to the original payment method and deduct it from the balance. If you believe this is a mistake, contact \[TBD: billing contact\]. ## Chargebacks and payment disputes Please contact us before disputing a payment with your bank: a refund of unused balance is usually faster. When a payment is disputed (a chargeback): 1. The organization is **frozen** automatically: its keys cannot create tasks while the dispute is open. 2. Unused cash balance is **deducted**, up to the disputed amount (and never more than was paid for that top-up minus what has already been refunded from it). If the balance does not cover it, the remainder is an amount you owe. 3. If the payment **stands** (the dispute is closed in our favor), we restore what was deducted and lift the freeze once the organization has no other open disputes. We do this after checking the outcome with the payment processor, so it is not immediate. 4. If the payment is **reversed** (the money goes back to the cardholder), the deduction stands and the organization stays frozen. \[TBD: whether and how the account can be reinstated after a lost chargeback\] To appeal a freeze, write to \[TBD: appeals contact\]. \[TBD: whether we email the owners automatically when a dispute freezes the organization (not built yet)\] ## Contact \[TBD: billing contact email\]