API reference
NullFlare API
The API solves Turnstile widgets and Cloudflare WAFv3 interstitial challenges. All solves are synchronous.
POST https://api.nullflare.com/
Content-Type: application/json
Authentication is the key property in the JSON body. Keep API
keys on your server; do not ship one in browser JavaScript.
Turnstile
Use Turnstile mode for a widget embedded in a page. The
sitekey and host must belong together.
Request
{
"key": "YOUR_KEY",
"sitekey": "0x4AAAAAAAExampleSitekey",
"host": "https://example.com/login"
}
Success
{
"status": "ok",
"token": "1.p5ESTZJ-kUZ46gYsJBAdzcVuf8Rysl...",
"elapsedMs": 4503,
"remaining": 9814
}
A Turnstile solve uses 1 credit.
Submit token to the target site in the field it uses for the
widget response, normally cf-turnstile-response. Tokens are
short-lived and single-use.
If the widget supplies action, cData, or
chlPageData, send the same values. An interactive challenge is
handled inside the solve; the request and response shapes do not change.
WAFv3
Use WAFv3 mode when a URL returns Cloudflare's challenge page (the “Just a moment…” interstitial) instead of the protected page. Do not provide a sitekey. The solver reads it from the challenge itself.
Request
{
"key": "YOUR_KEY",
"type": "WAFv3",
"host": "https://example.com/protected",
"proxy": "http://user:[email protected]:8080"
}
host is the URL that is currently showing the challenge.
Send a proxy with every WAFv3 solve, and use the same one for the requests that follow. Cloudflare ties the clearance to the address that solved it. Without a proxy, the solve runs from NullFlare's own address and the clearance is useless from yours. Use a sticky session proxy, not one that rotates per request.
Success
{
"status": "ok",
"token": "cf_clearance value",
"cfClearance": "cf_clearance value",
"userAgent": "Mozilla/5.0 ... Chrome/...",
"cookies": {
"cf_clearance": "...",
"__cf_bm": "..."
},
"elapsedMs": 8217,
"remaining": 9812
}
A WAFv3 clearance uses 2 credits: it runs the interstitial and its embedded Turnstile widget.
token and cfClearance contain the same clearance
value. token remains for clients that expect one result field;
new WAFv3 integrations should use the named session fields.
Using the session
- Send the next request with the exact returned
userAgent. - Send every entry in
cookies, not onlycf_clearance. Some zones also issue__cf_bmor other cookies. - Use the same proxy that was used for the solve.
- Request the challenged URL again. Preserve the returned session for later requests to the same zone until it expires.
A WAFv3 result is a session bundle. Mixing its cookie with a different User-Agent, cookie jar, or network identity can invalidate the clearance.
Request fields
| Field | Required | Description |
|---|---|---|
key | Always | Client API key. |
type | WAFv3 only | Turnstile is the default. Set this to WAFv3 for an interstitial challenge. Matching is case-insensitive. |
host | Always | Full page URL. For Turnstile, this is the page containing the widget. For WAFv3, this is the challenged URL. |
sitekey | Turnstile only | Widget sitekey. Omit it for WAFv3. |
proxy | WAFv3Turnstile: optional | Per-solve proxy. Every network request made by that solve uses it. See Proxies. |
action | When supplied by widget | Turnstile action string from the widget configuration. |
cData | When supplied by widget | Turnstile custom data. Preserve its exact value and casing. |
chlPageData | When supplied by widget | Turnstile page data. Preserve its exact value and casing. |
The request body must be a JSON object and may not exceed 256 KiB. Unknown fields are ignored, but relying on that is not part of the API contract.
Response contract
Responses produced by the API are JSON. Read the HTTP status before interpreting the body.
| Field | Meaning |
|---|---|
status | ok on success, error for a rejected or failed solve, and busy when capacity is temporarily unavailable. |
token | Turnstile response token, or the WAFv3 cf_clearance value. |
elapsedMs | Solver time in milliseconds. |
remaining | Credits left on the key after this request. It is null for an unlimited key. |
error | Public error description. Present on unsuccessful responses. |
errorCode | Stable machine-readable reason when the response has one, such as concurrency_limit, solve_failed, or node_unavailable. |
cfClearance | WAFv3 only. The clearance cookie value. |
userAgent | WAFv3 only. Browser identity bound to the session. |
cookies | WAFv3 only. Complete cookie jar observed during that solve. |
Errors and retries
| HTTP | Charged | What to do |
|---|---|---|
400 | No | Fix malformed JSON, missing fields, an invalid proxy, or another rejected input. Do not retry unchanged. |
401 | No | The key is missing or unknown. Correct the key. |
402 | No | The key does not have enough credits for this solve type. |
403 | No | The key is suspended. Contact the key administrator. |
413 | No | The request body is larger than 256 KiB. |
422 | No | The solve ran but produced no token or clearance (solve_failed). A retry is a new solve. |
429 | No | This key reached its simultaneous-solve limit. The body includes concurrencyLimit and inFlight. Wait for Retry-After, then retry. |
503 | No | The fleet is full, unavailable, or updating. Wait for Retry-After, then retry. |
Only successful solves are charged. Credits are reserved when a request is accepted and returned on every outcome other than a token or clearance, including 422.
If the connection is lost after the API accepts a request, its outcome may be unknown. Solve requests are not idempotent: retrying starts another solve, and both are charged if both succeed. HTTP 502, 504, and Cloudflare 52x pages originate between the client and this API rather than from the JSON API contract.
Proxies
http://host:port https://host:port socks4://host:port socks5://host:port http://username:password@host:port socks5://host:port:username:password
Both credential forms are accepted. In the colon form, everything after the username is treated as the password, so passwords may contain colons. SOCKS hostname resolution occurs at the proxy by default.
The proxy must tunnel the connection (HTTP CONNECT or SOCKS). A proxy that terminates TLS itself changes the browser fingerprint the challenge scores, and solves through it fail.
- Turnstile Optional. A rotating proxy works, and so
does omitting
proxyto solve from NullFlare's network. - WAFv3 Required in practice. Use a sticky session proxy and keep using it with the returned session.
Credits and concurrency
Credits cost $0.40 per 1,000. A Turnstile token uses 1 credit and a WAFv3 clearance uses 2. To buy credits or get a key, email [email protected] or ask on Discord.
Credits and simultaneous work are separate limits:
- Credits are used only by solves that return a token or clearance.
- Concurrency is the maximum number of that key's solves allowed in progress at once.
- Unlimited keys use no credits, return
remaining: null, and still obey their concurrency limit.
A client that fills its own concurrency slots receives 429. It does not consume another slot or prevent other keys from using the service.
Client examples
Turnstile with curl
curl https://api.nullflare.com/ \
--header 'Content-Type: application/json' \
--data '{
"key":"YOUR_KEY",
"sitekey":"0x4AAAAAAAExampleSitekey",
"host":"https://example.com/login"
}'
Turnstile with Python
import os
import requests
response = requests.post(
"https://api.nullflare.com/",
json={
"key": os.environ["NULLFLARE_KEY"],
"sitekey": "0x4AAAAAAAExampleSitekey",
"host": "https://example.com/login",
},
timeout=110,
)
result = response.json()
response.raise_for_status()
token = result["token"]
WAFv3 with server-side JavaScript
import { ProxyAgent } from "undici";
const proxy = process.env.OUTBOUND_PROXY;
const solveResponse = await fetch("https://api.nullflare.com/", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
key: process.env.NULLFLARE_KEY,
type: "WAFv3",
host: "https://example.com/protected",
proxy,
}),
signal: AbortSignal.timeout(110_000),
});
const session = await solveResponse.json();
if (!solveResponse.ok) throw new Error(session.error);
const cookie = Object.entries(session.cookies)
.map(([name, value]) => `${name}=${value}`)
.join("; ");
const protectedResponse = await fetch("https://example.com/protected", {
dispatcher: new ProxyAgent(proxy),
headers: {
"user-agent": session.userAgent,
"cookie": cookie,
},
});
The examples use a 110-second client timeout. Most solves finish in a few seconds; the API gives up on a solve after 90 seconds and still answers with JSON.
Health check
GET https://api.nullflare.com/health
{"status":"ok"}
This is an unauthenticated liveness check. It does not validate a client key, report fleet capacity, or use credits.