NSLSolver
API Reference

Kasada

Solve Kasada-protected origins. Requires the p.js path and FP/TL hosts; returns the x-kpsdk-* headers to replay on the target.

Kasada

Kasada uses fingerprinting and a proof-of-work challenge to gate the protected origin. The solver replays the full handshake and returns the x-kpsdk-* headers that grant access.

Request

FieldTypeRequiredNotes
typestringYeskasada (or alias kasada-bypass).
urlstringYesThe Kasada-protected origin.
user_agentstringNoRecommended — use the same UA you'll replay the headers with.
ua_versionintNoMajor Chrome version, 80300. Should match user_agent.
kasada_configobjectYesThe site-specific paths and hosts (see below).
proxystringNoOptional.

kasada_config

FieldRequiredNotes
p_js_pathYesPath to Kasada's p.js script, starting with /. No ... Max 512 chars.
fp_hostYesHostname serving the fingerprint page.
tl_hostYesHostname for the /tl token endpoint.
cd_constantNoHex string used as the CD proof-of-work constant when the site requires one.

Response

{
  "success": true,
  "type": "kasada",
  "headers": {
    "x-kpsdk-ct": "...",
    "x-kpsdk-cd": "{...JSON-encoded proof...}",
    "x-kpsdk-v": "j-1.2.xxx",
    "x-kpsdk-h": "..."
  },
  "cost": 0.0015
}

Forward the x-kpsdk-* headers verbatim on your next request to the protected origin. Always pair them with the same User-Agent you submitted in the solve call — Kasada fingerprints both.

How it works (under the hood)

  1. The solver fetches p.js to detect the Kasada SDK version.
  2. It loads the fingerprint page on fp_host and executes ips.js.
  3. It posts the fingerprint payload to tl_host/tl for tokens.
  4. It completes the /mfc handshake and computes the CD proof-of-work.
  5. The headers come back.

You don't need to drive any of this — you just need the config values.

Finding the config values

Most are visible in the browser's Network panel on the protected page:

  1. Filter for p.js — note the full path, that's p_js_path.
  2. The Host: header on that request is fp_host (usually the origin).
  3. Filter for /tl — the Host: header on that request is tl_host.

fp_host and tl_host are often the same hostname, but not always. When the target is an API subdomain (e.g. gql.twitch.tv), the TL host is usually that subdomain while the FP host is the app shell (e.g. passport.twitch.tv).

Performance

MetricTypical value
Solve time3-10 seconds
Success rate90%+

Solve time is dominated by the proof-of-work step; expect more variance than Turnstile.

Examples

import requests

ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"

r = requests.post(
    "https://api.nslsolver.com/solve",
    headers={"X-API-Key": "YOUR_KEY"},
    json={
        "type": "kasada",
        "url": "https://passport.twitch.tv",
        "user_agent": ua,
        "ua_version": 145,
        "kasada_config": {
            "p_js_path": "/149e9513-.../2d206a39-.../p.js",
            "fp_host": "passport.twitch.tv",
            "tl_host": "gql.twitch.tv",
        },
    },
    timeout=180,
)
solved = r.json()

session = requests.Session()
session.headers.update(solved["headers"])
session.headers["User-Agent"] = ua
# session.post("https://gql.twitch.tv/gql", json=...)
const ua = "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36";

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: "kasada",
    url: "https://passport.twitch.tv",
    user_agent: ua,
    ua_version: 145,
    kasada_config: {
      p_js_path: "/149e9513-.../2d206a39-.../p.js",
      fp_host: "passport.twitch.tv",
      tl_host: "gql.twitch.tv",
    },
  }),
  signal: AbortSignal.timeout(180_000),
});

const { headers } = await r.json();
type KasadaConfig struct {
    PJSPath    string `json:"p_js_path"`
    FPHost     string `json:"fp_host"`
    TLHost     string `json:"tl_host"`
    CDConstant string `json:"cd_constant,omitempty"`
}

type KasadaReq struct {
    Type         string       `json:"type"`
    URL          string       `json:"url"`
    UserAgent    string       `json:"user_agent"`
    UAVersion    int          `json:"ua_version"`
    KasadaConfig KasadaConfig `json:"kasada_config"`
}

payload, _ := json.Marshal(KasadaReq{
    Type: "kasada",
    URL:  "https://passport.twitch.tv",
    UserAgent: "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36",
    UAVersion: 145,
    KasadaConfig: KasadaConfig{
        PJSPath: "/149e9513-.../2d206a39-.../p.js",
        FPHost:  "passport.twitch.tv",
        TLHost:  "gql.twitch.tv",
    },
})

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: 180 * time.Second}
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"
    }
  }'

Troubleshooting

SymptomLikely causeFix
kasada_config required for kasada typeMissing the objectAlways include the config.
kasada_config.p_js_path contains invalid charactersBad pathMust start with /, no .., alphanumeric / _-/. only.
Empty headers returnedWrong p_js_path (Kasada SDK rev'd)Re-inspect the current p.js URL.
ua_version must be between 80 and 300Out-of-range versionUse the major Chrome number from your user_agent.
Headers rejected by targetUA mismatchReplay with the same UA you sent to /solve.

On this page