Call solvethree from a FastAPI backend

By solvethree4 min read

Return the task after creation, not after the entire solve. The caller retrieves its status until it’s ready or failed, then submits the token to the target if solving succeeded.

On this page

Configure the server and target

Use Python 3.10 or later. Install FastAPI with python -m pip install "fastapi[standard]", then install the official SDK with python -m pip install https://solvethree.com/downloads/solvethree_sdk-0.1.0-py3-none-any.whl.

Set these environment variables in the server process before starting the app:

  • SOLVETHREE_API_KEY: your workspace API key. Keep it on the server; the SDK uses it to authenticate with solvethree.
  • INTEGRATION_KEY: a separate secret callers send in the X-Integration-Key header to authenticate with your FastAPI routes.
  • CAPTCHA_SITE: the target hostname, not a full URL.
  • SOLVETHREE_PROXY: the HTTP or HTTPS proxy URL used for this task, without a path, query, or fragment.

Create an active profile for the target in your workspace before calling the route. The example fixes the action to login, so the profile and intended target request must match that use. Supplying a hostname doesn’t create a profile; see site profile setup.

Add and run the FastAPI routes

Save this example as main.py. Both routes require X-Integration-Key. The create route builds its task entirely from server configuration; callers don’t supply the site, action, or proxy in a request body.

FastAPI · run the synchronous SDK in a thread pool
# pip install "fastapi[standard]" and the official solvethree SDK wheel.
import hmac
import os
from fastapi import FastAPI, HTTPException, Request
from fastapi.concurrency import run_in_threadpool
from solvethree import Solvethree, SolvethreeError

app = FastAPI()

def authorize(request):
    if not hmac.compare_digest(request.headers.get("x-integration-key", ""), os.environ["INTEGRATION_KEY"]):
        raise HTTPException(status_code=401, detail="Unauthorized")

@app.post("/captcha/tasks")
async def create(request: Request):
    authorize(request)
    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"]
    def submit():
        return Solvethree(os.environ["SOLVETHREE_API_KEY"]).create_task(task, wait_ms=0, timeout=20)
    try:
        return await run_in_threadpool(submit)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid task input")
    except SolvethreeError as error:
        raise HTTPException(status_code=502, detail={"requestId": error.request_id, "taskId": error.task_id})

@app.get("/captcha/tasks/{task_id}")
async def retrieve(task_id: str, request: Request):
    authorize(request)
    def fetch():
        return Solvethree(os.environ["SOLVETHREE_API_KEY"]).get_task(task_id, wait_ms=15000, timeout=25)
    try:
        return await run_in_threadpool(fetch)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid task ID")
    except SolvethreeError as error:
        raise HTTPException(status_code=502, detail={"requestId": error.request_id, "taskId": error.task_id})

The task uses reCAPTCHA Enterprise v3 by default. It sends site, action, and one proxy, without a type or url field.

The Python SDK is synchronous. Calling it directly inside async def would block the event loop, so both handlers use run_in_threadpool. Creation uses wait_ms=0 with a 20-second SDK request timeout; retrieval allows a 15-second wait with a 25-second SDK request timeout. Neither handler waits through the whole solve using client.solve.

For local development, run fastapi dev main.py from the directory containing the file. The routes are POST http://127.0.0.1:8000/captcha/tasks and GET http://127.0.0.1:8000/captcha/tasks/{task_id}. FastAPI discovers the app object; there’s no separate URLconf to register.

Create a task, retrieve it, and recover interrupted requests

  1. 1.

    Create once with a stable request ID

    Send POST /captcha/tasks with X-Integration-Key and an X-Request-ID you retain for this logical task. The request ID must contain 1–64 ASCII letters, numbers, underscores, or hyphens. The route passes it to solvethree as requestId. Save the returned task id.

  2. 2.

    Check HTTP status before reading task status

    The wrapper returns 401 for failed authentication, 400 for ValueError input failures, and 502 for caught SolvethreeError exceptions. Its 502 detail contains requestId and taskId from the SDK error. Handle that HTTP error before treating the response as a task.

  3. 3.

    Retrieve the existing task

    Call GET /captcha/tasks/{task_id} with X-Integration-Key. Inspect status after each successful HTTP response. A retrieval can return a nonterminal task even after its wait ends; retrieve that same task again rather than creating another.

  4. 4.

    Recover uncertain creation

    If creation times out and you have a task ID, retrieve it. Otherwise, repeat the POST with your original X-Request-ID and unchanged server-side task inputs. Reusing that ID with the same task returns the existing task; changed inputs conflict. Keep the original ID separately rather than replacing it with an error response’s requestId.

What to do after a successful HTTP response
What to do after a successful HTTP response
Task statusCaller action
pending or solvingContinue retrieving the same task. There’s no usable solution yet.
readyRead solution and prepare the target request.
failedStop polling. Inspect errorCode and error before deciding whether a new task makes sense; failed solves are refunded.

Submit the token with the original proxy and returned identity

These routes return task data; they don’t submit anything to the target website. Once status is ready, the component making the target request must use solution.token, not the task ID.

Submit through the same proxy used to create that task, with the returned solution.userAgent and solution.secChUa browser identity. Retain the original task configuration rather than assuming the current environment still contains the same proxy. The SDK’s successful_proxy(original_task, ready_result) helper selects the proxy from that original task.

Keep the submission tied to the intended target and action. A ready task means solvethree generated a solution; only the target’s response tells you whether the application request was accepted.

Run your first task.

Start with $5 in workspace credit.

Create a workspace