Official Node and Python clients for the SolveGate API, which clears
Cloudflare Turnstile and Turnstile WAF challenges and returns a token. Both SDKs are
dependency-free and wrap auth, the error envelope, 429 backoff and async polling.
Get 1,000 free solves — no card required →
| Challenge | gate |
Notes |
|---|---|---|
| Turnstile — managed | turnstile |
The visible checkbox widget |
| Turnstile — non-interactive | turnstile |
Visible, never asks for a click |
| Turnstile — invisible | turnstile |
No widget; token lands in a hidden input |
| Turnstile WAF challenge page | waf |
The full-page "Just a moment…" interstitial |
reCAPTCHA (any version), hCaptcha, GeeTest, FunCaptcha, Arkose Labs, DataDome, Imperva and AWS WAF. If any of those appear in your flow, this is the wrong tool and a general-purpose service is the right one. The narrow surface is deliberate — it is why solves land in about a second.
Use this against properties you own or are explicitly authorised to test: your own staging and production apps, E2E and QA suites, CI pipelines, uptime and synthetic monitoring, and anti-bot configuration testing.
If the site is yours, read ## Testing without solving first —
Cloudflare publishes dummy sitekeys that make this SDK unnecessary in CI, and that is the
better answer for most test suites.
Pointing it at third-party properties in breach of their terms is prohibited by the Acceptable Use Policy and is enforced. See POLICY.md.
npm install solvegate # Node 18+
pip install solvegate # Python 3.9+import { SolveGate } from "solvegate";
const sg = new SolveGate(process.env.SOLVEGATE_KEY!); // sk_live_… or a free sk_test_… sandbox key
// Synchronous: wait for the token.
const solve = await sg.solve({
gate: "turnstile",
sitekey: "0x4AAAAAAAAA_target",
url: "https://app.example.com",
});
console.log(solve.token, solve.solve_ms);
// Asynchronous: submit, then poll.
const pending = await sg.solve({ gate: "waf", sitekey: "0x4…", url: "…", async: true });
const done = await sg.wait(pending.id, { timeoutMs: 60_000 });import os
from solvegate import SolveGate
sg = SolveGate(os.environ["SOLVEGATE_KEY"])
solve = sg.solve(
gate="turnstile",
sitekey="0x4AAAAAAAAA_target",
url="https://app.example.com",
)
print(solve.token, solve.solve_ms)
pending = sg.solve(gate="waf", sitekey="0x4…", url="…", async_=True) # note the trailing underscore
done = sg.wait(pending.id, timeout=60)If you control the application under test, you almost never need a solver in CI. Cloudflare
publishes dummy sitekeys that work on any domain, including localhost, and return a token
formatted XXXX.DUMMY.TOKEN.XXXX.
| Key | Type | Behaviour |
|---|---|---|
1x00000000000000000000AA |
sitekey, visible | Always passes |
1x00000000000000000000BB |
sitekey, invisible | Always passes |
2x00000000000000000000AB |
sitekey, visible | Always fails |
3x00000000000000000000FF |
sitekey, visible | Forces an interactive challenge |
1x0000000000000000000000000000000AA |
secret | Always passes validation |
2x0000000000000000000000000000000AA |
secret | Always fails validation |
3x0000000000000000000000000000000AA |
secret | Returns timeout-or-duplicate |
Swap both halves — a test secret key rejects real tokens and a live secret key rejects the dummy token. See Cloudflare's testing docs.
Solving is for what test keys cannot cover: a staging environment whose Turnstile config you do not control, or synthetic monitoring that must exercise the real production path.
Playwright — see playwright-turnstile
for a fixture that does this properly.
const sitekey = await page.locator("[data-sitekey]").first().getAttribute("data-sitekey");
const { token } = await sg.solve({ gate: "turnstile", sitekey, url: page.url() });
await page.evaluate((t) => {
document.querySelector<HTMLInputElement>('[name="cf-turnstile-response"]')!.value = t;
}, token);Puppeteer
const sitekey = await page.$eval("[data-sitekey]", (el) => el.dataset.sitekey);
const { token } = await sg.solve({ gate: "turnstile", sitekey, url: page.url() });
await page.evaluate((t) => {
document.querySelector('[name="cf-turnstile-response"]').value = t;
}, token);Selenium (Python)
sitekey = driver.find_element(By.CSS_SELECTOR, "[data-sitekey]").get_attribute("data-sitekey")
token = sg.solve(gate="turnstile", sitekey=sitekey, url=driver.current_url).token
driver.execute_script(
"document.querySelector('[name=\"cf-turnstile-response\"]').value = arguments[0]", token
)GitHub Actions — store the key as a secret and prefer a sk_test_ sandbox key in CI:
- run: npm test
env:
SOLVEGATE_KEY: ${{ secrets.SOLVEGATE_TEST_KEY }}See cloudflare-turnstile-e2e-example
for a complete runnable pipeline, and pytest-turnstile
for the Python equivalent.
Full reference at solvegate.io/docs · OpenAPI at solvegate.io/openapi.json.
POST /v1/solve
{ "gate": "turnstile", "sitekey": "0x4AAAAAAAAA_target", "url": "https://app.example.com" }Optional: action (the Turnstile action value), proxy (solve through your own egress IP),
async (submit and poll instead of waiting). Send an Idempotency-Key header to make a
retry safe.
{
"id": "slv_8Kd2aF9",
"status": "solved",
"gate": "turnstile",
"token": "0.Xa3f…X9_cleared",
"solve_ms": 980,
"expires_at": 1714663200,
"mode": "live",
"meter": "credits",
"billed": true
}GET /v1/solve/{id} retrieves a solve. Retrieval is free and never re-bills.
Errors carry a status, a stable code, a message, and whether the attempt was billed.
| Status | Code | Meaning |
|---|---|---|
| 402 | balance_empty |
Out of credits |
| 402 | pass_expired |
The unlimited pass window ended |
| 403 | forbidden_target |
Target refused by policy |
| 422 | unknown_sitekey |
Sitekey malformed or not found |
| 429 | rate_limited |
Over your rate limit; honour Retry-After |
| 504 | solve_timeout |
Not billed, safe to retry |
Both SDKs raise SolveGateError carrying status, code, message and billed.
Solves draw from prepaid credits, or from an unlimited pass if one is attached to the
key. Keys prefixed sk_test_ run in sandbox mode: they return a deterministic token
prefixed SANDBOX. and cost nothing, which is what you want in CI.
Responses carry RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and
RateLimit-Policy. On 429 both SDKs back off and retry automatically, honouring
Retry-After.
Prepaid credit packs; one credit is one successful solve.
| Pack | Price | Per 1,000 solves |
|---|---|---|
| 30k | $12 | $0.40 |
| 100k | $25 | $0.25 |
| 1M | $150 | $0.15 |
| 20M | $1,500 | $0.075 |
The first 1,000 solves are free, failed solves are never billed, and credits do not expire. Full ladder at solvegate.io/pricing.
Does it solve reCAPTCHA or hCaptcha? No. Turnstile and Turnstile WAF only.
Can I point it at a site I don't control? No — see Intended use.
What counts as a failed solve? Anything that returns no token: a timeout, an internal
retry that never lands, or a 5xx on our side. None of them are billed.
Do I need a paid key to run my test suite? Usually not. Use Cloudflare's test sitekeys
above, or a free sk_test_ sandbox key.
MIT — see LICENSE.