Call solvethree from a Flask backend

By solvethree4 min read

Use separate Flask routes to create and retrieve a solvethree task. The POST below returns without waiting for the whole solve; the caller keeps the task ID and checks the GET route until the task is ready or failed.

On this page

Install dependencies and configure the target

  1. 1.

    Use Python 3.10 or newer

    Install Flask with python -m pip install flask. Install the official Python SDK with python -m pip install https://solvethree.com/downloads/solvethree_sdk-0.1.0-py3-none-any.whl.

  2. 2.

    Set the server environment variables

    Set SOLVETHREE_API_KEY to your workspace API key and INTEGRATION_KEY to the secret your authorized caller will send in X-Integration-Key. Set CAPTCHA_SITE to the target hostname, not a full URL, and SOLVETHREE_PROXY to the HTTP or HTTPS proxy URL used for this task. Keep these values on the server.

  3. 3.

    Activate the site profile

    Configure an active profile for that hostname in the solvethree console, available to your workspace. Setting CAPTCHA_SITE doesn't create a profile. See site profile setup if the target isn't configured yet.

The example fixes the action to login and uses the default reCAPTCHA Enterprise v3 task. Change that action to match the target operation before running it. The caller cannot override the site, action, or proxy through the request body.

Add the create and retrieve routes

Flask · create and retrieve the same task
# pip install flask and the official solvethree SDK wheel.
import hmac
import os
from flask import Flask, abort, jsonify, request
from solvethree import Solvethree, SolvethreeError

app = Flask(__name__)

def client():
    return Solvethree(os.environ["SOLVETHREE_API_KEY"])

@app.before_request
def authorize():
    if not hmac.compare_digest(request.headers.get("x-integration-key", ""), os.environ["INTEGRATION_KEY"]):
        abort(401)

@app.errorhandler(SolvethreeError)
def task_error(error):
    return jsonify(requestId=error.request_id, taskId=error.task_id), 502

@app.errorhandler(ValueError)
def invalid_input(error):
    return jsonify(error="Invalid task input or ID"), 400

@app.post("/captcha/tasks")
def create():
    task = {"site": os.environ["CAPTCHA_SITE"], "action": "login", "proxy": os.environ["SOLVETHREE_PROXY"]}
    if request.headers.get("x-request-id"):
        task["requestId"] = request.headers["x-request-id"]
    return jsonify(client().create_task(task, wait_ms=0, timeout=20))

@app.get("/captcha/tasks/<task_id>")
def retrieve(task_id):
    return jsonify(client().get_task(task_id, wait_ms=15000, timeout=25))

Save the example as app.py. With the environment variables set, start it locally with python -m flask --app app run. The routes are POST /captcha/tasks and GET /captcha/tasks/<task_id>. Flask doesn't need a separate URLconf: the decorators register both routes. See the Flask quickstart for local startup details; keep your existing production WSGI configuration.

Creation uses wait_ms=0 with a 20-second network timeout. That skips waiting for the solve, not the HTTP request itself. Retrieval allows up to 15 seconds of waiting for a task update, with a 25-second network timeout. Both views use the synchronous SDK, so each occupies its request worker while the SDK call is running.

Keep the request ID before sending the POST

  1. 1.

    Create one request ID per logical task

    Store it before calling POST /captcha/tasks, then send it as X-Request-ID alongside X-Integration-Key. Use 1–64 ASCII letters, numbers, underscores, or hyphens. This POST needs no task body because the server constructs the task from its fixed configuration.

  2. 2.

    Keep the returned task ID

    After a successful HTTP response, save the result's id. Retrieve it through GET /captcha/tasks/<task_id>, sending X-Integration-Key again.

  3. 3.

    Recover an uncertain creation instead of creating another task

    If the POST connection times out, creation may still have succeeded. Retrieve the task if you have its ID. Otherwise, repeat the POST with the same X-Request-ID and unchanged task inputs to recover the existing task. Reusing the ID after changing the site, action, or proxy conflicts.

The header is optional in the sample, but omitting it removes this recovery option when creation succeeds and its response is lost. Don't generate a new ID merely because the first request timed out.

Handle HTTP errors before task status

Check the Flask response's HTTP status first. The example returns 401 for a missing or incorrect integration key, 400 for a caught ValueError, and 502 for a caught SolvethreeError. That 502 body exposes requestId and taskId for recovery; it isn't a task-status payload and doesn't by itself mean the task failed.

After a successful HTTP response, inspect the task's status.
After a successful HTTP response, inspect the task's status.
Task statusCaller action
pendingKeep the same task ID and retrieve again.
solvingKeep the same task ID and retrieve again.
readyStop polling and read the solution.
failedStop polling and inspect errorCode and error. Failed solves are refunded.

Submit the token with the original proxy and returned identity

Only a ready result contains a usable solution. Send solution.token to the intended target—not the task ID—and use the returned solution.userAgent and solution.secChUa browser identity.

Submit through the same proxy used to create the task. This example uses one fixed SOLVETHREE_PROXY, so retain that original proxy configuration with the task rather than assuming the current environment value will still match later. The Flask routes return the SDK result; they don't perform the target submission or configure the caller's proxy.

Keep the token, proxy, browser identity, and intended target together. A ready task means solvethree generated a solution; acceptance is determined by the target's response to the subsequent application request.

Run your first task.

Start with $5 in workspace credit.

Create a workspace