01API reference capzilla.pro

The whole API is one endpoint.

Send a solve request; the token comes back in the same response. No task IDs, no polling loop, no second round trip.

Quickstart

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.

Authentication

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
Regenerating a key from the dashboard revokes the old one immediately. There is no grace period — roll it when your clients can be restarted.

POST /solve

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.

Request

FieldTypeNotes
typestringCaptcha type slug — see below. Required.
sitekeystringThe target's public key. Required for most types.
siteurlstringThe page the widget is embedded on. Required.
actionstringreCAPTCHA v3 action name, when the site sets one.
proxystringSolve through your own exit: http://user:pass@host:port.

Response

{
  "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.

Captcha types & their fields

Read live from this instance — what you see here is what is enabled right now.

Loading…

GET /balance

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.

Rate limits & CPM

Two separate limits, and they fail differently:

LimitMeaningWhen exceeded
CPMSolves per minute. Your plan's ceiling, 100 – 10,000. Requests are held briefly, then 429.
ThreadsSolves 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.

Errors

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.

CodeMeaningWhat to do
400Bad request, unknown or disabled type, missing fieldFix the request. Retrying will not help.
401Missing or invalid API keyCheck the header. Was the key regenerated?
402Out of balanceTop up or buy a pack. Stop sending until you do.
403Account disabledContact support.
429Over CPM or out of threadsBack off and retry — see below.
502The solve itself failedRetry. This one is expected occasionally.

Retries

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.

Code samples

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"}'
import os, requests

r = requests.post(
    "https://capzilla.pro/solve",
    headers={"X-API-Key": os.environ["CAPZILLA_KEY"]},
    json={
        "type":    "turnstile",
        "sitekey": "0x4AAA…",
        "siteurl": "https://target.com",
    },
    timeout=60,
)
r.raise_for_status()
token = r.json()["solution"]
const res = await fetch("https://capzilla.pro/solve", {
  method:  "POST",
  headers: {
    "X-API-Key":    process.env.CAPZILLA_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type:    "turnstile",
    sitekey: "0x4AAA…",
    siteurl: "https://target.com",
  }),
});
if (!res.ok) throw new Error(`capzilla ${res.status}`);
const { solution } = await res.json();
body, _ := json.Marshal(map[string]any{
    "type":    "turnstile",
    "sitekey": "0x4AAA…",
    "siteurl": "https://target.com",
})
req, _ := http.NewRequest("POST", "https://capzilla.pro/solve", bytes.NewReader(body))
req.Header.Set("X-API-Key", os.Getenv("CAPZILLA_KEY"))
req.Header.Set("Content-Type", "application/json")

res, err := (&http.Client{Timeout: 60 * time.Second}).Do(req)
if err != nil { return "", err }
defer res.Body.Close()

var out struct{ Solution string `json:"solution"` }
json.NewDecoder(res.Body).Decode(&out)
$ch = curl_init("https://capzilla.pro/solve");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 60,
    CURLOPT_HTTPHEADER     => [
        "X-API-Key: " . getenv("CAPZILLA_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        "type"    => "turnstile",
        "sitekey" => "0x4AAA…",
        "siteurl" => "https://target.com",
    ]),
]);
$token = json_decode(curl_exec($ch), true)["solution"];
Something missing from these docs? Tell us what you were looking for — it gets added.
02 Common questions
Do I have to poll for the result?

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.

What happens if a solve fails?

You get a 502 and you are not charged. Retry it.

Can I use my own proxies?

Yes — pass proxy on the request. Without it the solve goes out through ours.

How fast can I send?

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.

Does credit expire?

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.