Skip to content
ZeroCaptcha

Python SDK

The official ZeroCaptcha client for Python: create a Cloudflare Turnstile task or a Cloudflare challenge page’s task, wait for its result, read your balance, and check a task callback’s signature. It uses the standard library only, and runs on Python 3.9 and later.

Every task is real and paid from your prepaid balance, and only a task that succeeds is charged.

Terminal window
pip install zerocaptcha

The package is not on PyPI yet. Until it is, call the API with requests, as the quickstart’s Python program does: it makes the same calls.

Give the client your API key (zc_live_…, from the dashboard’s API keys page) and the API’s address. Keep both in your environment rather than in your code.

import os
from zerocaptcha import TaskFailedError, ZeroCaptcha
client = ZeroCaptcha(api_key=os.environ["ZEROCAPTCHA_KEY"], base_url=os.environ["ZEROCAPTCHA_API"])
# Create a task and wait for its token: one call.
try:
token = client.solve(
website_url="https://shop.example.com/login", # the page with the widget
website_key="0x4AAAAAAAB1cD2eF3gH4iJ5", # its data-sitekey
# The widget's data-action and data-cdata, or the action and cData options of
# turnstile.render(). Leave out any the widget does not set.
action="login",
cdata="session-7f3a9c2e",
# proxy="http://user:pass@proxy.example.net:8080", # to solve through your own proxy
# callback_url="https://hooks.example.com/zerocaptcha", # to be called when it ends
)
print(token)
except TaskFailedError as failed:
print(failed.code) # such as ERROR_CAPTCHA_UNSOLVABLE; nothing was charged
# Or step by step.
task = client.create_task(
website_url="https://shop.example.com/login",
website_key="0x4AAAAAAAB1cD2eF3gH4iJ5",
action="login", # the widget's data-action, if it sets one
cdata="session-7f3a9c2e", # the widget's data-cdata, if it sets one
# Your ID for this task, sent as the Idempotency-Key; one is made for you when you give none.
idempotency_key="login-2026-10-01-0001",
)
done = client.wait_for_result(task["id"], timeout=120)
print(done["solution"]["token"], done["cost"])
# Your balance, in US dollars.
print(client.get_balance()["available"])
Method What it does
ZeroCaptcha(api_key, base_url) A client.
create_task(website_url, website_key, type=, action=, cdata=, proxy=, callback_url=, idempotency_key=) Creates a task.
get_task(task_id) Reads a task; its token is in ["solution"] while it is available.
wait_for_result(task_id, timeout=180, interval=2) Polls until the task ends.
solve(website_url, website_key, **task) create_task then wait_for_result: the token.
create_challenge_task(website_url, proxy, callback_url=, idempotency_key=) Creates a Cloudflare challenge page’s task, through your proxy.
solve_challenge(website_url, proxy) create_challenge_task then wait_for_result: {"cf_clearance", "user_agent", "token_expires_at"}.
get_balance() {"available", "held", "currency"}, as decimal strings.
verify_signature(secret, header, raw_body, tolerance=300) Whether a callback is genuine.
  • Tasks come back as dictionaries, as the API writes them (id, status, cost, solution…).
  • proxy="http://user:pass@proxy.example.net:8080" solves a task through your proxy.
  • create_task sends an Idempotency-Key with every call, one of its own unless you give idempotency_key, so retrying it never makes a second task.
  • A request the API asks you to slow down (429) or cannot serve for a moment (502, 503, 504) is tried again after the wait it asks for, three times in all, as is one that got no answer or an answer cut short, with the same Idempotency-Key. Any other refusal raises ZeroCaptchaError with the API’s code, such as insufficient_funds, and its request_id.
  • wait_for_result never runs past timeout: each read gets only the time left, and a retry that would wait longer than that is not made. It raises TaskFailedError when the task fails or expires; a wait that runs out raises WaitTimeoutError, with the task as last read (task, None if no read finished in time), and you can wait again.

A challenge page is passed through your proxy, and gives the cf_clearance cookie with the user agent it is bound to. Send both, through the same proxy:

clearance = client.solve_challenge(
"https://shop.example.com/",
os.environ["PROXY_URL"], # such as http://user:pass@proxy.example.net:8080
)
print(clearance["cf_clearance"], clearance["user_agent"])

A task created with callback_url is POSTed to it once it ends, with the task as JSON. Check each call’s signature against the raw body, before you parse it. With Flask:

import json
import os
from flask import Flask, abort, request
from zerocaptcha import verify_signature
app = Flask(__name__)
@app.post("/zerocaptcha/callback")
def callback():
if not verify_signature(
os.environ["ZEROCAPTCHA_CALLBACK_SECRET"],
request.headers.get("ZeroCaptcha-Signature"),
request.get_data(), # the raw bytes, before parsing
):
abort(401)
task = json.loads(request.get_data())
print(task["id"], task["status"])
return "", 204

A call older than five minutes does not verify, so a recorded call cannot be replayed. See Polling and callbacks.