Python SDK
More
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.
Install
Section titled “Install”pip install zerocaptchaThe 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_tasksends anIdempotency-Keywith every call, one of its own unless you giveidempotency_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 raisesZeroCaptchaErrorwith the API’scode, such asinsufficient_funds, and itsrequest_id. wait_for_resultnever runs pasttimeout: each read gets only the time left, and a retry that would wait longer than that is not made. It raisesTaskFailedErrorwhen the task fails or expires; a wait that runs out raisesWaitTimeoutError, with the task as last read (task,Noneif no read finished in time), and you can wait again.
Cloudflare challenge pages
Section titled “Cloudflare challenge pages”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"])Callbacks
Section titled “Callbacks”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 jsonimport os
from flask import Flask, abort, requestfrom 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 "", 204A call older than five minutes does not verify, so a recorded call cannot be replayed. See Polling and callbacks.