Skip to content

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

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 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. In TypeScript they are checked against the task type, and task.solution has the type’s solution type.

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

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.

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

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 handle

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.