A small REST API that solves Cloudflare Turnstile challenges. It drives a real browser under the hood (patchright / camoufox), keeps a pool of browsers ready, and hands you back a token. Supports proxies and stores results in SQLite.
- A simple POST endpoint that returns a Turnstile token
- A pool of browsers so you can solve several at once
- Works with Chromium, Chrome, Edge or Camoufox
- Optional proxy support (
proxies.txt) - Random or fixed browser fingerprint
You'll need Python 3.8+ and one browser installed.
python -m venv venv
source venv/bin/activate # on Windows: venv\Scripts\activate
pip install -r requirements.txtThen grab a browser (just pick one):
python -m patchright install chromium # Chromium
python -m patchright install msedge # Edge
python -m camoufox fetch # Camoufoxpython api_solver.py --host 0.0.0.0 --port 9797If you're on a headless server, run it behind a virtual display first:
Xvfb :99 -screen 0 1920x1080x24 &
DISPLAY=:99 python api_solver.py --host 0.0.0.0 --port 9797 --debugWant a fixed browser instead of a random one? Pin it:
python api_solver.py --browser edge --version 138| Flag | Default | What it does |
|---|---|---|
--no-headless |
False | Show the browser window |
--useragent |
None | Custom User-Agent |
--debug |
False | Verbose logging |
--browser_type |
chromium | chromium, chrome, msedge or camoufox |
--thread |
4 | How many browsers in the pool |
--proxy |
False | Use a random proxy from proxies.txt |
--random |
False | Random User-Agent / Sec-CH-UA |
--browser |
None | Pin a browser name (chrome, edge, ...) |
--version |
None | Pin a browser version (138, 139, ...) |
--host |
0.0.0.0 | Host to bind |
--port |
9797 | Port to listen on |
Send a POST to /turnstile with a JSON body. The call blocks until it's solved
and gives you the token right back — no polling needed.
import requests
session = requests.Session()
url = "https://huha.hainahola.com/turnstile"
payload = {
"url": "https://testing-turnstile.hainahola.com",
"sitekey": "0x4AAAAAADtHnx9Uq1taS3wW",
"action": "submit",
"cdata": "" # optional
}
headers = {
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())url and sitekey are required, action and cdata are optional.
When it works you get:
{
"errorId": 0,
"status": "ready",
"solution": {
"token": "0.KBtT-r..."
}
}And if it can't solve it:
{
"errorId": 1,
"errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
"errorDescription": "Workers could not solve the Captcha"
}Drop one proxy per line into proxies.txt. Any of these formats work:
ip:port
ip:port:username:password
scheme://ip:port
scheme://username:password@ip:port
This is for learning and personal use. Whatever you do with it is on you — play nice and respect the sites you hit.
Pull requests are welcome. If you open one, please describe your changes properly in the PR description so it's clear what you changed and why.