NSLSolver
API Reference

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
HeaderValueRequired
Content-Typeapplication/jsonYes
X-API-KeyYour API keyYes

The request body is JSON and must be 1 MB or smaller. Larger bodies are rejected before parsing.

Body — shared fields

FieldTypeRequiredNotes
typestringYesOne of turnstile, challenge, kasada, akamai. Long aliases cloudflare-turnstile, cloudflare-challenge, kasada-bypass, and akamai-bypass are also accepted.
urlstringYesThe page where the captcha is loaded. Must be http/https and must not point at an internal address. Max 2048 chars.
proxystringvariesprotocol://[user:pass@]host:port where protocol is http, https, socks4, or socks5. Required for challenge and akamai.
user_agentstringvariesUser-Agent to use during solving. Max 512 chars. Required for akamai; recommended otherwise.

Body — per type

FieldTypeRequiredNotes
site_keystringYesThe data-sitekey value on the target page. [a-zA-Z0-9_-], max 128 chars.
actionstringNoEchoed from data-action if the site uses it. Max 256 chars.
cdatastringNoEchoed from data-cdata if the site uses it. Max 512 chars.
proxystringNoOptional — Turnstile rarely needs one.
{
  "type": "turnstile",
  "site_key": "0x4AAAAAAAB...",
  "url": "https://example.com/login",
  "action": "login",
  "cdata": "session-id-123"
}
FieldTypeRequiredNotes
proxystringYesThe clearance cookie is bound to this proxy's egress IP.
{
  "type": "challenge",
  "url": "https://example.com/protected",
  "proxy": "http://user:[email protected]:8080"
}
FieldTypeRequiredNotes
user_agentstringNoRecommended — match the UA you'll use when replaying the headers.
ua_versionintNoMajor Chrome version (80300). Should match user_agent.
kasada_configobjectYesThe Kasada-specific paths and hosts (see below).
proxystringNoOptional.

kasada_config fields:

FieldTypeRequiredNotes
p_js_pathstringYesPath to Kasada's p.js script. Must start with /. Max 512.
fp_hoststringYesHostname serving the fingerprint page.
tl_hoststringYesHostname for the /tl token endpoint.
cd_constantstringNoHex 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"
  }
}
FieldTypeRequiredNotes
user_agentstringYesMust match the UA you'll replay the returned cookies with.
proxystringYesThe 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
}
FieldTypeNotes
successboolAlways true on 200.
typestringEchoes the request type.
tokenstringSubmit as the captcha response (e.g. cf-turnstile-response).
costnumberUSD 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

StatusMeaning
400Validation failed. The error message names the offending field.
401Missing or invalid API key.
402Insufficient balance.
403Type not allowed, IP not on the allowlist, or the URL/domain is banned.
429CPM rate limit exceeded (see Rate limits).
503The 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

Turnstile
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"
  }'
Challenge
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"
  }'
Kasada
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.

On this page