POST /solve
The single endpoint that solves Turnstile, Challenge, Kasada, and Akamai captchas. Synchronous — the response body contains the solved token, cookies, or headers.
POST /solve
Submit a captcha for solving. The connection stays open until the solver returns a result. Responses always include success and type; the rest depends on the captcha kind.
POST https://api.nslsolver.com/solve| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
X-API-Key | Your API key | Yes |
The request body is JSON and must be 1 MB or smaller. Larger bodies are rejected before parsing.
Body — shared fields
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | Yes | One of turnstile, challenge, kasada, akamai. Long aliases cloudflare-turnstile, cloudflare-challenge, kasada-bypass, and akamai-bypass are also accepted. |
url | string | Yes | The page where the captcha is loaded. Must be http/https and must not point at an internal address. Max 2048 chars. |
proxy | string | varies | protocol://[user:pass@]host:port where protocol is http, https, socks4, or socks5. Required for challenge and akamai. |
user_agent | string | varies | User-Agent to use during solving. Max 512 chars. Required for akamai; recommended otherwise. |
Body — per type
| Field | Type | Required | Notes |
|---|---|---|---|
site_key | string | Yes | The data-sitekey value on the target page. [a-zA-Z0-9_-], max 128 chars. |
action | string | No | Echoed from data-action if the site uses it. Max 256 chars. |
cdata | string | No | Echoed from data-cdata if the site uses it. Max 512 chars. |
proxy | string | No | Optional — Turnstile rarely needs one. |
{
"type": "turnstile",
"site_key": "0x4AAAAAAAB...",
"url": "https://example.com/login",
"action": "login",
"cdata": "session-id-123"
}| Field | Type | Required | Notes |
|---|---|---|---|
proxy | string | Yes | The clearance cookie is bound to this proxy's egress IP. |
{
"type": "challenge",
"url": "https://example.com/protected",
"proxy": "http://user:[email protected]:8080"
}| Field | Type | Required | Notes |
|---|---|---|---|
user_agent | string | No | Recommended — match the UA you'll use when replaying the headers. |
ua_version | int | No | Major Chrome version (80–300). Should match user_agent. |
kasada_config | object | Yes | The Kasada-specific paths and hosts (see below). |
proxy | string | No | Optional. |
kasada_config fields:
| Field | Type | Required | Notes |
|---|---|---|---|
p_js_path | string | Yes | Path to Kasada's p.js script. Must start with /. Max 512. |
fp_host | string | Yes | Hostname serving the fingerprint page. |
tl_host | string | Yes | Hostname for the /tl token endpoint. |
cd_constant | string | No | Hex constant for the CD proof-of-work, when the site requires one. |
{
"type": "kasada",
"url": "https://passport.twitch.tv",
"user_agent": "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36",
"ua_version": 145,
"kasada_config": {
"p_js_path": "/149e9513-.../2d206a39-.../p.js",
"fp_host": "passport.twitch.tv",
"tl_host": "gql.twitch.tv"
}
}| Field | Type | Required | Notes |
|---|---|---|---|
user_agent | string | Yes | Must match the UA you'll replay the returned cookies with. |
proxy | string | Yes | The returned _abck cookie is bound to this proxy's egress IP. |
{
"type": "akamai",
"url": "https://www.target.com/login",
"user_agent": "Mozilla/5.0 ... Chrome/144.0.0.0 Safari/537.36",
"proxy": "http://user:[email protected]:8000"
}Responses
200 — Turnstile
{
"success": true,
"type": "turnstile",
"token": "0.AkBr7...",
"cost": 0.0008
}| Field | Type | Notes |
|---|---|---|
success | bool | Always true on 200. |
type | string | Echoes the request type. |
token | string | Submit as the captcha response (e.g. cf-turnstile-response). |
cost | number | USD deducted from your balance for this call. |
200 — Challenge
{
"success": true,
"type": "challenge",
"cookies": { "cf_clearance": "abc123..." },
"user_agent": "Mozilla/5.0 ...",
"cost": 0.001
}The response may also include a token field when the target serves a Turnstile-like inner challenge — handle both shapes if you target diverse sites.
200 — Kasada
{
"success": true,
"type": "kasada",
"headers": {
"x-kpsdk-ct": "...",
"x-kpsdk-cd": "...",
"x-kpsdk-v": "j-1.2.xxx",
"x-kpsdk-h": "..."
},
"cost": 0.0015
}Replay these headers (with your User-Agent) on the next request to the Kasada-protected origin.
200 — Akamai
{
"success": true,
"type": "akamai",
"cookies": {
"_abck": "...",
"bm_sz": "...",
"ak_bmsc": "..."
},
"cost": 0.0020
}Replay the returned cookies on the protected origin paired with the same User-Agent and same proxy/exit IP you submitted. See Akamai for details.
Error responses
| Status | Meaning |
|---|---|
| 400 | Validation failed. The error message names the offending field. |
| 401 | Missing or invalid API key. |
| 402 | Insufficient balance. |
| 403 | Type not allowed, IP not on the allowlist, or the URL/domain is banned. |
| 429 | CPM rate limit exceeded (see Rate limits). |
| 503 | The solver couldn't complete this request — safe to retry. Not billed. |
See Errors for the full list of messages and recovery guidance.
Billing on failure
Balance is checked up front, but only deducted after a successful solve. Anything that returns a non-2xx is free of charge.
Full examples
curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "turnstile",
"site_key": "0x4AAAAAAA",
"url": "https://example.com/login"
}'curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "challenge",
"url": "https://example.com/protected",
"proxy": "http://user:[email protected]:8080"
}'curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "kasada",
"url": "https://passport.twitch.tv",
"user_agent": "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36",
"ua_version": 145,
"kasada_config": {
"p_js_path": "/149e9513-.../2d206a39-.../p.js",
"fp_host": "passport.twitch.tv",
"tl_host": "gql.twitch.tv"
}
}'import os, requests
resp = requests.post(
"https://api.nslsolver.com/solve",
headers={"X-API-Key": os.environ["NSL_API_KEY"]},
json={
"type": "turnstile",
"site_key": "0x4AAAAAAA",
"url": "https://example.com/login",
},
timeout=120,
)
data = resp.json()
if not data["success"]:
raise RuntimeError(f"solve failed ({resp.status_code}): {data['error']}")
print(data["token"], data["cost"])const r = await fetch("https://api.nslsolver.com/solve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NSL_API_KEY,
},
body: JSON.stringify({
type: "turnstile",
site_key: "0x4AAAAAAA",
url: "https://example.com/login",
}),
signal: AbortSignal.timeout(120_000),
});
const data = await r.json();
if (!data.success) throw new Error(`${r.status}: ${data.error}`);
console.log(data.token, data.cost);type solveReq struct {
Type string `json:"type"`
SiteKey string `json:"site_key,omitempty"`
URL string `json:"url"`
}
type solveResp struct {
Success bool `json:"success"`
Token string `json:"token,omitempty"`
Cost float64 `json:"cost,omitempty"`
Error string `json:"error,omitempty"`
}
payload, _ := json.Marshal(solveReq{
Type: "turnstile",
SiteKey: "0x4AAAAAAA",
URL: "https://example.com/login",
})
req, _ := http.NewRequest("POST", "https://api.nslsolver.com/solve", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("NSL_API_KEY"))
client := &http.Client{Timeout: 120 * time.Second}
resp, err := client.Do(req)
if err != nil { /* network failure — retry */ }
defer resp.Body.Close()
var out solveResp
_ = json.NewDecoder(resp.Body).Decode(&out)
if !out.Success {
log.Fatalf("solve failed (%d): %s", resp.StatusCode, out.Error)
}
fmt.Println(out.Token, out.Cost)Client timeouts
Configure your HTTP client to wait at least 120s (180s for Kasada). The server itself accepts up to 180s of writing time; cutting your client lower means you may abandon a solve that would have succeeded — you still won't be billed, but you waste throughput.
Authentication
Every API call authenticates via the X-API-Key header. Keys carry balance, allowed captcha types, CPM ceiling, and an optional IP allowlist.
GET /balance
Read the live balance and limits for an API key. Returns balance, unlimited flag, allowed captcha types, and the current CPM bucket state.