SDKs
Official client libraries for the native API (v1). 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 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 | TypedDicts 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
Section titled “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.
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)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 withidempotency_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,
429and5xxresponses (including a proxy’s502or504) are retried (2 times by default), waitingRetry-Afterwhen 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 asks. - Timeout and cancellation.
solvegives up after 5 minutes by default (timeoutin seconds in Python,timeoutMsin Node.js), including the create request; once the task exists it throwsWaitTimeoutErrorwith the task ID, and the task itself goes on. Cancel with athreading.Event(cancel=) or anAbortSignal(signal):solvethen throwsOperationCancelledErrorwith the task ID (or, if the task was not created yet, the idempotency key). WithAsyncNoptcha, cancel the coroutine as usual. - asyncio.
AsyncNoptcharuns 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:orclient.close()).
The parameters of each captcha are on its task type page. In TypeScript they are checked against
the task type, and task.solution has the type’s solution type.
Step by step
Section titled “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 whether the answer worked.
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 failedprint(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))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 failedconsole.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.
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
Section titled “Errors”Each error code 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.
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 missingexcept InvalidParamsError as e: for err in e.errors: print(err["pointer"], err["message"]) print("request id:", e.request_id) # quote it when you contact supportimport { 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
Section titled “Webhooks”Both libraries verify webhook 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).
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 handleimport { 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}Versions
Section titled “Versions”The libraries follow Semantic Versioning. New task types, fields and error codes arrive in minor
releases; the API stays backward compatible within /v1 (Versioning). The compatibility API and
the 2captcha legacy API are not covered: they exist for software that already speaks those protocols.