Send a solve request; the token comes back in the same response. No task IDs, no polling loop, no second round trip.
Two things: an API key from your dashboard, and one POST.
curl -s https://capzilla.pro/solve \
-H "X-API-Key: $CAPZILLA_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"turnstile","sitekey":"0x4AAA…","siteurl":"https://target.com"}'
The response carries the token. Submit it to the target site exactly as a browser would.
Send your key in the X-API-Key header. It also accepts key in the
JSON body or ?key= in the query string, for clients that cannot set headers —
prefer the header, since query strings end up in logs and proxy history.
X-API-Key: cz_live_xxxxxxxxxxxxxxxxxxxxxxxx
Send type plus that type's fields. The request blocks until the solve is done
— typically under two seconds, and it holds one of your threads until it returns.
| Field | Type | Notes |
|---|---|---|
| type | string | Captcha type slug — see below. Required. |
| sitekey | string | The target's public key. Required for most types. |
| siteurl | string | The page the widget is embedded on. Required. |
| action | string | reCAPTCHA v3 action name, when the site sets one. |
| proxy | string | Solve through your own exit: http://user:pass@host:port. |
{
"success": true,
"solution": "0.Ab3xK…", // submit this to the target site
"time_ms": 410,
"balance": 9.84 // USD remaining, after this solve
}
balance is returned on every solve, so a long-running client can track its own
spend without ever calling /balance.
Read live from this instance — what you see here is what is enabled right now.
Loading…
Everything about the account's standing in one call: credit, what the next solve draws on,
and the limits it is held to. Authenticated the same way as /solve.
curl -s https://capzilla.pro/balance -H "X-API-Key: $CAPZILLA_KEY"
{
"active": true,
"balance": 9.84,
"currency": "USD",
"solves_remaining": 0, // prepaid pack, spent before balance
"plan": "Custom 2000 CPM",
"unlimited": false,
"billing": "balance", // unlimited | solve_quota | balance
"max_cpm": 2000, // null means no ceiling
"max_threads": 40,
"total_solves": 184203,
"cpm_plan": "Custom 2000 CPM",
"cpm_until": "2026-09-07T00:00:00Z"
}
billing tells you which pot the next solve comes out of, in the order the
server actually applies: an unlimited subscription first, then prepaid solves, then
balance. cpm_plan and cpm_until appear only while a purchased
speed plan is running, so a client can see its rate is about to drop instead of
discovering it as sudden throttling.
Two separate limits, and they fail differently:
| Limit | Meaning | When exceeded |
|---|---|---|
| CPM | Solves per minute. Your plan's ceiling, 100 – 10,000. | Requests are held briefly, then 429. |
| Threads | Solves in flight at the same instant. | Immediate 429. |
CPM is a smoothed bucket, not a hard per-minute cliff: a short burst above your rate is absorbed rather than rejected, and only sustained overload turns into errors. That means you do not need to pace requests precisely — send them as you have them.
Raise CPM from the plan builder; raise threads by asking.
Both current values are on /balance.
Any failure returns a non-200 with {"success":false,"error":"…"}. The status
is the part to branch on; the string is for humans and may change.
| Code | Meaning | What to do |
|---|---|---|
| 400 | Bad request, unknown or disabled type, missing field | Fix the request. Retrying will not help. |
| 401 | Missing or invalid API key | Check the header. Was the key regenerated? |
| 402 | Out of balance | Top up or buy a pack. Stop sending until you do. |
| 403 | Account disabled | Contact support. |
| 429 | Over CPM or out of threads | Back off and retry — see below. |
| 502 | The solve itself failed | Retry. This one is expected occasionally. |
Retry 429 and 502. Never retry 400, 401 or 402 — the same request will fail the same way and, on 402, a retry storm is how an outage turns into a bill.
for attempt in range(4):
r = requests.post(URL, headers=H, json=body, timeout=60)
if r.status_code == 200:
return r.json()["solution"]
if r.status_code in (400, 401, 402, 403):
r.raise_for_status() # never retry these
time.sleep(2 ** attempt * 0.25) # 0.25s, 0.5s, 1s
Set a client timeout of at least 60 seconds. A solve is normally well under two, but the request is synchronous and a hard target under load can take longer — a short timeout turns a solve you have already paid for into a failure on your side.
curl -s https://capzilla.pro/solve \
-H "X-API-Key: $CAPZILLA_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"turnstile","sitekey":"0x4AAA…","siteurl":"https://target.com"}'
No. POST /solve returns the token in the same response. There is no task ID
and no second request — the connection stays open for the second or so it takes.
You get a 502 and you are not charged. Retry it.
Yes — pass proxy on the request. Without it the solve goes out through ours.
Up to your plan's CPM, from 100 to 10,000 solves per minute. Short bursts above it are
absorbed rather than rejected. Your current ceiling is on /balance.
No. Balance and prepaid solves stay until you use them. Speed plans are the only thing
with a clock, and /balance reports when one lapses.