Solve multiple CAPTCHAs in Python and Node.js

By solvethree3 min read

Limit the number of active solves, not just the number of requests you start. The examples below run eight independent reCAPTCHA Enterprise tasks with at most four solves active at once, handling each result as it finishes.

Four is an example concurrency setting, not a solvethree capacity limit. Choose a limit that fits how quickly your application can use the returned solutions.

On this page

Prepare the SDK and task inputs

Use Python 3.10+ or Node.js 22+. Install the official SDK from the download URL in the documentation; use the direct package download. Keep the workspace API key on the server.

  • Set SOLVETHREE_API_KEY and SOLVETHREE_PROXY in the server environment.
  • Replace accounts.example.com and login with your target hostname and action. The site must already have an active profile available to your workspace; submitting a hostname doesn't create one.
  • Treat each entry in jobs as a separate logical task. The examples repeat the same site, action, and proxy to keep the batch readable, but give every task a distinct requestId.
  • Keep each task's inputs unchanged during recovery. A new independent solve needs a new ID; recovering an interrupted solve needs the original ID and identical inputs.

Run a bounded batch

Python · four simultaneous solves
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from uuid import uuid4
from solvethree import Solvethree

# Each worker gets its own synchronous client.
def solve_one(job):
    client = Solvethree(os.environ["SOLVETHREE_API_KEY"])
    return client.solve({**job, "requestId": str(uuid4())}, timeout=180)

jobs = [
    {"site": "accounts.example.com", "action": "login", "proxy": os.environ["SOLVETHREE_PROXY"]}
    for _ in range(8)
]
with ThreadPoolExecutor(max_workers=4) as pool:
    futures = [pool.submit(solve_one, job) for job in jobs]
    for future in as_completed(futures):
        try:
            result = future.result()
            print("Ready task:", result["id"])
            # Submit this solution promptly; don't wait for the whole batch.
        except Exception as error:
            print("One task failed:", type(error).__name__)

Python needs threads here because `Solvethree` is synchronous. Each solve call creates a task and waits for a ready result or raises. There is no native async client, so calling it directly inside an async function would still block that function's event-loop thread. The example uses four thread-pool workers, each with its own client, and handles completed futures without waiting for the entire batch.

Node.js limits concurrency through four worker promises. Each worker awaits one solve before taking another job. Promise.all waits for those workers; it doesn't start every job at once. By contrast, mapping an unbounded job list directly to solve calls starts the whole list immediately and gives you no useful concurrency cap.

Both examples catch errors per task so one failed solve doesn't stop the remaining jobs. The 180-second timeout applies to each solve call, not to the whole batch.

Use each ready result immediately

Replace the success-path comment with your target submission before running a real batch. The printed ID identifies the task; it isn't the reCAPTCHA token. Submit solution.token with the returned userAgent and secChUa, using the same proxy supplied for that task.

Don't collect every token and wait for the slowest solve before using them. Handle each ready result where it arrives. The SDK solves the CAPTCHA; it doesn't submit the solution to your target, and a ready task doesn't prove that the target accepted the subsequent request.

Recover one interrupted task—not the whole batch

The examples print error class names to keep the worker pattern short. In an integration, retain the affected job and the recovery IDs exposed by SDK errors: Python uses request_id and task_id; JavaScript uses requestId and taskId.

A solve times out or loses its connection.

Check the error for a task ID or request ID. The remote task may still exist.

Try this: Resume GET polling when you know the task ID. Otherwise, recover creation by replaying identical inputs with the same request ID.

The SDK raises TaskFailedError.

Inspect the terminal task's errorCode and error.

Try this: Stop polling that task and address the reported cause before starting another solve. Don't blindly retry unchanged invalid inputs.

Rerunning either example generates fresh request IDs and starts new logical tasks; it isn't recovery. Both SDKs avoid automatically retrying task creation, so keep enough information to recover only the interrupted job rather than resubmitting successful work.

Run your first task.

Start with $5 in workspace credit.

Create a workspace