API reference

Solve a captcha

One authenticated POST submits the solve and returns a job id; poll it for the result. Authenticate with your API key in the x-api-key header. Only successful solves are billed.

Endpoint

POSThttps://api.kagedcap.io/v2/solve
FieldTypeRequiredDescription
taskstringyesThe solve type — one of the tasks listed below.
urlstringmost tasksThe page URL the captcha appears on. Optional for Kasada. For evaluate, auth.* hosts default to verify_phone; anything else to join_queue.
sitekeystringreCAPTCHAThe reCAPTCHA site key. Not needed for tmpt, evaluate, or Kasada (applied server-side).
actionstringtmpt onlyRequired for tmpt (Event or Login). Optional everywhere else — for reCAPTCHA v3 it binds the token to your site’s action, and omitting it performs a no-action solve; for evaluate it overrides the action derived from the URL. Not used by v2 or Kasada.
proxystringper taskRequired for non-ProxyLess tasks, evaluate, and KasadaLogin; omit for ProxyLess and KasadaReload.
phone_numberstringevaluateverify_phone only. Must include the country prefix (e.g. +12025550123). Goes into context.phoneNumber and sizes keystroke telemetry.
queueIdstringevaluatejoin_queue only. Goes into context.queueId — e.g. "examplequeue01". Omitted when empty.
eventIdstringevaluatejoin_queue only. Goes into context.eventId — e.g. "1A2B3C4D5E6F7A8B".
userAgentstringnoA real desktop UA, used by reCAPTCHA, tmpt and evaluate. The token embeds it, so match the browser you replay with. For evaluate it also selects the device profile — screen, GPU and client hints move with it — so a UA matching your traffic scores better than none. Kasada ignores it and returns its own.
secretKeystringnoYour reCAPTCHA secret, to get a verified score back.
sitestringKasadaKasada site flow to solve (e.g. "footlocker"). Defaults server-side.
kpsdk_stnumberKasadaReloadServer timestamp from a prior KasadaLogin — the base for the refreshed x-kpsdk-cd.
hashstringKasadaReloadSession PoW hash (sessionHash) from the prior KasadaLogin — required to compute a valid x-kpsdk-cd.
x_kpsdk_ctstringKasadaReloadSession token from the prior KasadaLogin. Required — its leading chars seed the cd; echoed back unchanged.
x_kpsdk_vstringKasadaReloadSDK version from the prior KasadaLogin, echoed back unchanged.
x_kpsdk_hstringKasadaReloadSignature from the prior KasadaLogin, echoed back unchanged.

Always send a real desktop userAgent — the token embeds it, so it should match the browser your traffic presents. A mismatched or missing UA lowers the score; when omitted, a default is used.

Solving is submit and poll: the POST answers immediately with a job id, then you poll for the result. Waiting costs you no socket and costs us no capacity, so a burst queues instead of coming back as concurrency_limit_exceeded. Every task family goes through this one endpoint — reCAPTCHA, tmpt, Evaluate and Kasada — and a finished job carries that family's full result, exactly as the solve-type cards below show.

The body is the request above. Send an Idempotency-Key header and a resent submit returns the original job instead of buying a second solve — the key is durable and works across shards, so a retry after a network blip is free.

202 Accepted — queued, not finished
{ "success": true, "id": "8f14e45f-ceea-467a-9e4b-2c1d3a5f7b90", "status": "running" }
GEThttps://api.kagedcap.io/v2/solve/{id}

Poll about every 5 seconds. Read status, never success — success only turns true once status is done, so treating it as the completion test reports a still-running solve as a failure. running means keep going; done and failed are terminal.

200 response — done (reCAPTCHA / tmpt)
{
  "success": true,
  "id": "8f14e45f-ceea-467a-9e4b-2c1d3a5f7b90",
  "status": "done",
  "task": "ReCaptchaV3EnterpriseTask",
  "token": "03AFcWeA...",
  "score": 0.9,
  "solve_ms": 1840,
  "elapsed_ms": 2110,
  "created_at": "2026-09-10T00:00:00Z",
  "completed_at": "2026-09-10T00:00:02Z"
}
200 response — done (Evaluate)
{ "success": true, "status": "done", "task": "EvaluateTask", "token": "<epsf allow token>", "decision": "allow" }
200 response — failed
{ "success": false, "id": "8f14e45f-...", "status": "failed", "error": "solver_unavailable" }
FieldTypeDescription
idstringThe job id, echoed back. Scoped to your account — another account’s id answers 404.
statusstringrunning, done, or failed. This is the completion test — not success, which only turns true once status is done.
tokenstring | nullThe solved token, for reCAPTCHA, tmpt and Evaluate. Null while running, and null again once the result expires.
scorenumber | nullreCAPTCHA only — the v3 score, when the gateway has one.
verificationobject | nullreCAPTCHA only — the verification block, when present.
decisionstringEvaluate only — allow, challenge, or block.
errorstringOnly on failed — one of the codes listed under Errors.
solve_msnumberTime the solver itself took, once the job is done.
elapsed_msnumberWall clock from submit to completion, including any time queued.
created_atstringWhen the job was accepted (ISO 8601).
completed_atstringWhen it finished. Absent while running.

Two things to design around. Results expire: the result is cleared about five minutes after completion — a reCAPTCHA token is dead in roughly two anyway — so a late poll returns done with the result fields gone rather than something you can use; read it promptly. Detect completion by status, not by the token: the token is null while a job is still running, so only status === "done" tells you it is ready.

Direct fleet endpoints (advanced)

You never have to think about this. api.kagedcap.io/v2/solve reads the task and forwards it to the right solver fleet for you. If you're latency-sensitive and already know which fleet a request targets, you can skip that routing hop and submit straight to the fleet subdomain — same path, same body, same x-api-key header, identical response. Polling needs no such care: the job store is shared, so GET /v2/solve/{id} answers from any host.

FleetEndpointTasks
reCAPTCHArecap.kagedcap.io/v2/solveall ReCaptcha* tasks (v2/v3, enterprise, proxyless)
TMPTtmpt.kagedcap.io/v2/solveTicketmasterTmptTask
Evaluateevaluate.kagedcap.io/v2/solveEvaluateTask
Kasadakasada.kagedcap.io/v2/solveKasadaLogin, KasadaReload

Send each task to its own fleet — a task posted to the wrong subdomain is rejected, whereas the default endpoint always routes it correctly. When in doubt, keep using api.kagedcap.io/v2/solve; the routing overhead is a few milliseconds and it never sends a task to the wrong place.

Solve types

The task field selects the type. Each card shows the body you submit and the result a finished job carries once status is done. reCAPTCHA and tmpt return the same envelope — success, token, score, verification — with score populated only for v3. Evaluate adds decision (allow / challenge / block). Kasada returns its own envelope (the x-kpsdk-* headers), shown below.

reCAPTCHA v3

ReCaptchaV3TaskReCaptchaV3TaskProxyLess

Invisible, score-based. Returns a token and a 0.0–1.0 score. Requires a sitekey; action is optional and binds the token to your site’s action when supplied.

Request body
{
  "task": "ReCaptchaV3Task",
  "url": "https://example.com/login",
  "sitekey": "6Lc…",
  "action": "login",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "proxy": "http://user:pass@1.2.3.4:8888"
}
Result — in the poll's done response
{
  "success": true,
  "task": "ReCaptchaV3Task",
  "token": "03AFcWeA…",
  "score": 0.9,
  "verification": null
}

The ProxyLess variant (ReCaptchaV3TaskProxyLess) must omit proxy.

reCAPTCHA v3 Enterprise

ReCaptchaV3EnterpriseTaskReCaptchaV3EnterpriseTaskProxyLess

Enterprise sitekeys. Same shape as v3 — a sitekey and an action.

Request body
{
  "task": "ReCaptchaV3EnterpriseTask",
  "url": "https://www.example.com",
  "sitekey": "6Lc…",
  "action": "checkout",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "proxy": "http://user:pass@1.2.3.4:8888"
}
Result — in the poll's done response
{
  "success": true,
  "task": "ReCaptchaV3EnterpriseTask",
  "token": "03AFcWeA…",
  "score": 0.9,
  "verification": null
}

reCAPTCHA v2 · Checkbox and Invisible

ReCaptchaV2TaskReCaptchaV2TaskProxyLess

Both v2 widget shapes. No action and no score — a v2 token is pass/fail via Google siteverify, so score is always null. Send "invisible": true for an invisible sitekey.

Request body
{
  "task": "ReCaptchaV2Task",
  "url": "https://example.com/",
  "sitekey": "6Lc…  (a v2 sitekey)",
  "invisible": true,
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "proxy": "http://user:pass@1.2.3.4:8888"
}
Result — in the poll's done response
{
  "success": true,
  "task": "ReCaptchaV2Task",
  "token": "03AGdBq…",
  "score": null,
  "challenged": false,
  "verification": null
}

No action is required. `invisible` tells us which widget shape the sitekey is: true for invisible, false for checkbox. Omit it and we work it out from the widget, which costs an extra round trip — so send it when you know. Getting it wrong fails fast rather than silently solving the other shape. `challenged` reports whether an image challenge had to be answered; checkbox keys usually present one.

TMPT Solver

TicketmasterTmptTask

Mints a tmpt cookie. url must be an event or login page on the target site and action must be Event or Login. No sitekey (applied server-side); proxy is optional. Send the userAgent you will replay the cookie with — it is used for the underlying solve, so a mismatch weakens the result. The returned token is the tmpt cookie value.

Request body
{
  "task": "TicketmasterTmptTask",
  "url": "https://www.example.com/event/…",
  "action": "Event",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "proxy": "http://user:pass@1.2.3.4:8888"
}
Result — in the poll's done response
{
  "success": true,
  "task": "TicketmasterTmptTask",
  "token": "eyJhbGciOi…",
  "score": null,
  "verification": null
}

Score verification does not apply, so score and verification are always null.

Evaluate

EvaluateTask

Runs the APS protect() flow — mints the page reCAPTCHA, posts /epsf/v1/evaluate, and returns the APS decision token. url and proxy are required. No sitekey (applied server-side). userAgent is optional but recommended — it picks the device profile the fingerprint is built from, so send the UA you will replay with. auth.* URLs default to verify_phone; any other supported host defaults to join_queue. The returned token is the EPSF allow token (replay it on the next APS step).

Request body
// verify_phone — the default for auth.* hosts
{
  "task": "EvaluateTask",
  "url": "https://auth.example.com/as/authorization.oauth2?client_id=…&redirect_uri=https://identity.example.com/exchange",
  "proxy": "http://user:pass@1.2.3.4:8888",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "phone_number": "+12025550123"
}

// join_queue — the default for any other supported host
{
  "task": "EvaluateTask",
  "url": "https://www.example.com/event/1A2B3C4D5E6F7A8B",
  "proxy": "http://user:pass@1.2.3.4:8888",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
  "queueId": "examplequeue01",
  "eventId": "1A2B3C4D5E6F7A8B"
}
Result — in the poll's done response
{
  "success": true,
  "task": "EvaluateTask",
  "token": "eyJhbGciOi…",
  "decision": "allow"
}

The action is inferred from the URL host — pass action explicitly only to override it. decision is allow, challenge, or block; challenge means APS wants MFA/PoW/Persona next.

Kasada · Login

KasadaLogin

Returns the full Kasada header set (x-kpsdk-ct/cd/v/h) plus the identity headers (user-agent + client hints) to replay, and reload — Kasada's trust verdict (true = high-trust token). Requires a proxy — the token is IP-bound. No sitekey; site defaults server-side. Keep kpsdk_st, hash, and the x_kpsdk_* values to drive a later KasadaReload.

Request body
{
  "task": "KasadaLogin",
  "site": "footlocker",
  "proxy": "http://user:pass@1.2.3.4:8888"
}
Result — in the poll's done response
{
  "success": true,
  "task": "KasadaLogin",
  "site": "footlocker",
  "headers": {
    "user-agent": "Mozilla/5.0 …",
    "sec-ch-ua": "\"Chromium\";v=\"131\", …",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\""
  },
  "x_kpsdk_ct": "…",
  "x_kpsdk_cd": "…",
  "x_kpsdk_v": "…",
  "x_kpsdk_h": "…",
  "kpsdk_st": 1712345678901,
  "hash": "d49a6b7b…",
  "reload": true,
  "user_agent": "Mozilla/5.0 …"
}

Unlike reCAPTCHA/tmpt there is no token — send the returned headers with your request. Reuse the same proxy for the request the token was minted for. Keep hash + kpsdk_st + x_kpsdk_ct to refresh the cd via KasadaReload.

Kasada · Reload

KasadaReload

Refreshes x-kpsdk-cd from a prior KasadaLogin — no proxy, no browser (a few hashes). Send back kpsdk_st, hash, and x_kpsdk_ct (all required) from the login response; the cd is recomputed for that exact token. Use it to keep a session fresh.

Request body
{
  "task": "KasadaReload",
  "kpsdk_st": 1712345678901,
  "hash": "d49a6b7b…",
  "x_kpsdk_ct": "…",
  "x_kpsdk_v": "…",
  "x_kpsdk_h": "…"
}
Result — in the poll's done response
{
  "success": true,
  "task": "KasadaReload",
  "x_kpsdk_ct": "…",
  "x_kpsdk_cd": "…",
  "x_kpsdk_v": "…",
  "x_kpsdk_h": "…",
  "kpsdk_st": 1712345678901,
  "hash": "d49a6b7b…"
}

x_kpsdk_ct/v/h and hash are echoed unchanged; only x_kpsdk_cd changes. x_kpsdk_ct must be the token the refreshed cd will ride with — its leading chars are folded into the cd.

Check balance

GEThttps://api.kagedcap.io/v1/balance
200 response
{
  "amount_micros": "3000000",
  "held_micros": "2000",
  "available_micros": "2998000",
  "display": "$2.998",
  "subscriptions": [
    {
      "id": "7f1c…",
      "package": "v3 Enterprise · 50k / week",
      "sku": "v3_enterprise",
      "period": "week",
      "status": "active",
      "quota": 50000,
      "used": 12345,
      "remaining": 37655,
      "period_start": "2026-09-21T14:03:11.000Z",
      "period_end": "2026-09-28T14:03:11.000Z",
      "renews_at": "2026-09-28T14:03:11.000Z",
      "expires_at": null
    }
  ]
}

Balance is stored in micros — 1,000,000 micros = $1.00. Only successful solves are billed; a per-solve price is quoted on your dashboard.

subscriptions lists the solve packages on the key you called with — packages belong to one key, not the account — and is empty when it has none. status is active, canceling or past_due. renews_at is when the next period starts and used resets; it is null once canceled, when expires_at says when the package ends instead.

Billing & top-ups

Add funds from your dashboard → Top up. Top-ups are a signed-in action, not an API call — there's no endpoint to script with your key.

◦Card: pay by card through Stripe. Balance is credited the moment the payment confirms — usually instant.
◦Crypto: pay with BTC, ETH, USDC and more via a hosted checkout. Your balance is credited when the payment is confirmed on-chain (typically a few minutes), never at redirect — so it's safe to close the tab once you've sent.
◦Promo codes: apply a code at checkout to add a bonus to a qualifying top-up.
◦Solve packages: a fixed number of solves of one type every week or month, billed by card and attached to one API key from dashboard → Packages. Those solves don't touch your balance. Unused solves reset at renewal, and a key that runs out gets 402 subscription_quota_exhausted until then rather than being billed per solve. Rotating the key moves its package to the new secret; revoking it stops the renewal.
◦Dedicated plans: steady, high-volume traffic can move to a monthly dedicated fleet instead of per-solve billing — see pricing on your dashboard or ask support.
◦Pinning keys to servers: on a dedicated plan you can optionally pin an API key to some of your servers from dashboard → API keys → Servers. A pinned key only uses those servers (and waits on them when they are busy); unpinned keys use all of your servers. If none of a key's pinned servers handle a task's fleet, it falls back to all of your servers for that fleet. Changes apply within a minute.

SDKs

Official clients for five languages — each handles auth, task selection, and errors. Pass version: "v2" for reCAPTCHA v2 invisible; enterprise for the Enterprise family; use kasadaLogin / kasadaReload for Kasada (the login session carries its headers into the reload for you).

All repos on GitHub ↗

Every method submits to /v2/solve, polls to completion, and hands you that fleet's full result — so a call still blocks until the solve finishes, without holding a connection open for the wait. Low-level submit/poll primitives are there when you want to drive the wait yourself.

TypeScriptnpm install @kagedcap/sdk
GitHub ↗
import { KagedCapClient } from '@kagedcap/sdk';
const kc = new KagedCapClient(process.env.KAGEDCAP_API_KEY!);

// v3 (enterprise)
const { token } = await kc.solve({ sitekey, url, action, enterprise: true, proxy });
// v2 invisible — no action
const v2 = await kc.solve({ sitekey, url, version: 'v2', proxy });

// Kasada — the session keeps the headers and resends them on reload
const k = await kc.kasadaLogin({ site: 'footlocker', proxy });
const fresh = await kc.kasadaReload(k);
Node.jsnpm install kagedcap
GitHub ↗
const { KagedCapClient } = require('kagedcap');
const kc = new KagedCapClient(process.env.KAGEDCAP_API_KEY);

const { token } = await kc.solve({ sitekey, url, action, enterprise: true, proxy });
const v2 = await kc.solve({ sitekey, url, version: 'v2', proxy });

const k = await kc.kasadaLogin({ site: 'footlocker', proxy });
const fresh = await kc.kasadaReload(k); // reuses k's kpsdk_st + x_kpsdk_*
Pythonpip install kagedcap
GitHub ↗
from kagedcap import KagedCapClient
kc = KagedCapClient(api_key)

res = kc.solve(sitekey=…, url=…, action="login", enterprise=True, proxy=proxy)
v2  = kc.solve(sitekey=…, url=…, version="v2", proxy=proxy)

k = kc.kasada_login(site="footlocker", proxy=proxy)
fresh = kc.kasada_reload(k)  # resends k's kpsdk_st + x_kpsdk_*
Gogo get github.com/kagedcap/kagedcap-sdk-go/v2
GitHub ↗
kc := kagedcap.New(apiKey)

res, _ := kc.Solve(kagedcap.SolveParams{Sitekey: …, URL: …, Action: "login", Enterprise: true, Proxy: proxy})
v2,  _ := kc.Solve(kagedcap.SolveParams{Sitekey: …, URL: …, Version: "v2", Proxy: proxy})

login, _ := kc.KasadaLogin(kagedcap.KasadaParams{Site: "footlocker", Proxy: proxy})
fresh, _ := kc.KasadaReload(login) // reuses login's kpsdk_st + x_kpsdk_*
Rustcargo add kagedcap
GitHub ↗
let kc = KagedCapClient::new(api_key);

let res = kc.solve(SolveParams { sitekey, url, action, enterprise: true, proxy, ..Default::default() })?;
let v2  = kc.solve(SolveParams { sitekey, url, version: Some("v2".into()), proxy, ..Default::default() })?;

let login = kc.kasada_login(KasadaParams { site: Some("footlocker".into()), proxy, ..Default::default() })?;
let fresh = kc.kasada_reload(&login)?; // reuses login's kpsdk_st + x_kpsdk_*

AI agents

Let an AI coding agent wire KagedCap into your project. Drop in the skill below and your agent installs the right SDK for your language, generates a typed wrapper around your KAGEDCAP_API_KEY, and adds a connectivity test — no copy-pasting from these docs.

Download SKILL.md ↓
◦Claude Code: save it as .claude/skills/kagedcap-sdk/SKILL.md in your repo (or under ~/.claude/skills/ to use it everywhere), then ask it to integrate the KagedCap SDK.
◦Cursor & other agents: it is plain Markdown — add it as a project rule, or paste it into whatever agent scaffolds the integration.
◦Grab it from the CLI: curl -o SKILL.md https://kagedcap.io/kagedcap-sdk.skill.md

Errors

Errors arrive in two places. Request-time errors — auth, validation, balance, limits — come back as the HTTP status on the POST. Solve outcomes arrive in the poll: a job with status failed and error set to one of the codes below; for those rows the HTTP column shows the class the code belongs to, not a status your poll returns.

HTTPCodeMeaning
401unauthorizedMissing or invalid API key.
403account_suspendedAccount suspended — contact support.
403host_not_allowedThis key's allowlist doesn't include the page host.
404not_foundNo such job — the id is malformed, expired, or belongs to another account. Only GET /v2/solve/{id} returns this.
400validation_errorBad request shape (see message).
400validation_error (proxy)A proxy paired with the wrong task variant — ProxyLess tasks must omit it, every other reCAPTCHA task requires it. Read the issues array; there is no dedicated code.
400proxy_invalidPre-flight rejection: the proxy was malformed, used a disallowed scheme, did not resolve, or pointed at a private address. No solve attempted.
402insufficient_fundsTop up your balance.
402key_spend_cap_reachedThis key hit its spend cap.
402subscription_quota_exhaustedThis key's solve package is used up for the period. It resets at renewal — the key doesn't fall back to balance.
402subscription_past_dueThis key's package renewal payment failed. Update your card in the dashboard.
409idempotency_conflictA request with this Idempotency-Key is already in flight. Wait for it to finish or send a new key.
429concurrency_limit_exceededAt your concurrency limit — extra solves briefly queue; a 429 means the queue filled or the wait timed out. Back off and retry.
429key_frozenKey auto-frozen after a burst of failed solves (usually bad proxies). Wait the cool-off in the message, then retry.
429rate_limitedToo many requests too fast. Back off and retry.
429overloadedWe are briefly saturated — back off and retry shortly.
422proxy_unreachableThe solve ran and your proxy did not answer (not billed). Fix the proxy — the same one will fail again. Was 502 before 2026-09-24.
500internal_errorSomething failed on our side (not billed). Retry with backoff; if it persists, contact support.
502solve_failedThe solve failed (not billed). Do NOT retry the same request.
503no_capacityNo solver capacity — retry shortly.
503solver_unavailableA solver was assigned but could not be reached (not billed). Safe to submit again.
503maintenanceSolving is paused for maintenance — retry shortly.
503proxyless_disabledProxyLess tasks are temporarily disabled — resend the task with your own proxy.
504solve_timeoutThe solve timed out (not billed).

Rate limits & retries

◦Billing: only a job that finishes done with a result is charged. Failures, timeouts, and rejections are free.
◦Concurrency: each account has a max number of in-flight solves. Past it, extra solves are briefly queued rather than rejected — you just see a slightly slower response as one of your in-flight solves frees a slot. You only get 429 concurrency_limit_exceeded if the queue is full or the wait times out; then back off and retry.
◦Frozen keys: a burst of failed solves (usually dead/blocked proxies) can temporarily freeze a key — its requests return 429 key_frozen with a cool-off in the message. Fix the proxies and wait it out; a refused request never reaches the failure counter, so retrying does not extend the freeze — but it does not shorten it either.
◦Retry policy: retry a submit that returns 429 or 503 with backoff. A job that ends failed with solver_unavailable, no_capacity or solve_timeout is safe to submit again; one that ends with solve_failed or proxy_unreachable ran a real attempt — fix the input first, which for the second is always the proxy.
◦Idempotency: send an Idempotency-Key header to make a retried request safe. Pass x-request-id to correlate logs; it's echoed back on every response.
◦Check a solve: GET https://api.kagedcap.io/v1/solves/{request_id} returns a past result — useful if your client disconnected after a successful solve.
SupportAPI & SDK docs — KagedCap