Complete reference aligned with the official CapSkip API.
CapSkip exposes a standard captcha-solver HTTP API on your local machine:
POST http://<host>:<port>/in.php → submit captcha
GET http://<host>:<port>/res.php → poll result
The SDK only supports the captcha types documented by CapSkip.
| Type | SDK method | method (POST) |
|---|---|---|
| Image captcha | normal() |
post or base64 |
| reCAPTCHA v2 | recaptcha(..., version='v2') |
userrecaptcha |
| reCAPTCHA v3 | recaptcha(..., version='v3') |
userrecaptcha + version=v3 |
| Cloudflare Turnstile | turnstile() |
turnstile |
| GeeTest v3 (slide) | geetest() |
geetest |
Proxy is supported for reCAPTCHA, Turnstile, and GeeTest — not for image captcha.
from capskip import CapSkip
solver = CapSkip(
apiKey="capskip",
host="127.0.0.1",
port=8080,
defaultTimeout=120,
recaptchaTimeout=300,
pollingInterval=5, # max seconds between polls; starts at 0.25s and backs off to this
)| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | CapSkip API key |
method |
string | Yes | post (multipart file) or base64 |
file |
file | Yes* | Image file when method=post |
body |
string | Yes* | Base64 image when method=base64 |
json |
int | No | 0 plain text (default), 1 JSON |
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | CapSkip API key |
action |
string | Yes | get |
id |
int | Yes | Captcha ID from in.php |
json |
int | No | 0 plain text (default), 1 JSON |
result = solver.normal("captcha.png")
result = solver.normal("https://example.com/captcha.jpg")
result = solver.normal("data:image/png;base64,iVBORw0KGgo...", json=1)
print(result["code"])Only json is accepted as an extra parameter. Proxy is not supported.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key |
string | Yes | — | CapSkip API key |
method |
string | Yes | — | userrecaptcha |
googlekey |
string | Yes | — | Site key (data-sitekey / k) |
pageurl |
string | Yes | — | Full page URL |
enterprise |
int | No | 0 |
1 = Enterprise v2 |
invisible |
int | No | 0 |
1 = Invisible reCAPTCHA |
data-s |
string | No | — | Google Search / services data-s value |
json |
int | No | 0 |
1 = JSON response |
proxy |
string | No | — | IP:PORT or login:pass@IP:PORT |
proxytype |
string | No | HTTP |
HTTP, HTTPS, SOCKS5, SOCKS5H |
Do not send version, action, or min_score for v2.
Same as image captcha poll parameters.
# Standard v2
result = solver.recaptcha(sitekey="...", url="https://example.com")
# Invisible v2
result = solver.recaptcha(sitekey="...", url="...", invisible=1)
# Enterprise v2
result = solver.recaptcha(sitekey="...", url="...", enterprise=1)
# Enterprise v2 with data-s (SDK alias: datas=...)
result = solver.recaptcha(sitekey="...", url="...", enterprise=1, datas="...")
# With proxy
result = solver.recaptcha(
sitekey="...",
url="...",
proxy={"type": "HTTPS", "uri": "user:pass@1.2.3.4:3128"},
)| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key |
string | Yes | — | CapSkip API key |
method |
string | Yes | — | userrecaptcha |
version |
string | Yes | — | v3 |
googlekey |
string | Yes | — | Site key |
pageurl |
string | Yes | — | Full page URL |
enterprise |
int | No | 0 |
1 = Enterprise v3 |
action |
string | No | verify |
Action from grecaptcha.execute() |
min_score |
float | No | 0.4 |
Minimum acceptable score |
json |
int | No | 0 |
1 = JSON response |
proxy |
string | No | — | Proxy address |
proxytype |
string | No | HTTP |
Proxy type |
Do not send invisible for v3.
result = solver.recaptcha(
sitekey="...",
url="https://example.com",
version="v3",
action="submit",
min_score=0.7, # or SDK alias: score=0.7
enterprise=0,
)| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | CapSkip API key |
method |
string | Yes | turnstile |
sitekey |
string | Yes | Turnstile sitekey |
pageurl |
string | Yes | Full page URL |
action |
string | No | From data-action or turnstile.render() |
data |
string | No | cData / data-cdata |
pagedata |
string | No | chlPageData (challenge pages) |
json |
int | No | 0 plain text, 1 JSON |
proxy |
string | No | Proxy address |
proxytype |
string | No | Proxy type |
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | CapSkip API key |
action |
string | Yes | get |
id |
int | Yes | Captcha ID |
json |
int | Yes | Must be 1 to receive User-Agent |
The SDK automatically polls Turnstile results with json=1 and includes userAgent in the result when CapSkip returns it.
# Standalone widget
result = solver.turnstile(sitekey="0x4AAAAAAA...", url="https://example.com")
print(result["code"])
print(result.get("userAgent")) # present when CapSkip returns it
# Challenge page
result = solver.turnstile(
sitekey="0x4AAAAAAA...",
url="https://example.com",
action="managed",
data="cData_value",
pagedata="chlPageData_value",
)
# Use result["userAgent"] when submitting the token| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | CapSkip API key |
method |
string | Yes | geetest |
gt |
string | Yes | Static per-site GeeTest id |
challenge |
string | Yes | Single-use challenge token |
pageurl |
string | Yes | Full page URL |
api_server |
string | No | GeeTest API server domain, e.g. api-na.geetest.com |
json |
int | No | 0 plain text, 1 JSON |
proxy |
string | No | Proxy address |
proxytype |
string | No | Proxy type |
Both come from the target site, which fetches them from an endpoint returning
{"gt": "...", "challenge": "..."} (often .../register.php or a gettype/get.php
request). Find it in DevTools → Network, or read them out of the
initGeetest({ gt, challenge }) call in the page scripts.
challengeis single-use and expires in about a minute. Fetch a fresh pair immediately before each solve. If a solve comes back with a bad-challenge error, request a new pair and retry — reusing one never succeeds.
result = solver.geetest(
gt="81388ea1fc187e0c335c0a8907ff2625",
challenge="7cf6a8b1a2c34d5e6f7089abcdef0123",
url="https://example.com/login",
)
result["challenge"] # geetest_challenge
result["validate"] # geetest_validate
result["seccode"] # geetest_seccode
result["code"] # the same answer as a raw JSON stringPost the three fields back exactly as the site's own front-end would:
requests.post(LOGIN_URL, data={
"geetest_challenge": result["challenge"],
"geetest_validate": result["validate"],
"geetest_seccode": result["seccode"],
})GeeTest is a real browser solve, so it uses the longer recaptchaTimeout budget
rather than defaultTimeout.
Every solve method returns:
{
"captchaId": "12345",
"code": "TOKEN_OR_TEXT",
"userAgent": "..." # Turnstile only, when json=1 poll includes it
}GeeTest additionally expands its answer into challenge, validate, and
seccode (code keeps the raw JSON string):
{
"captchaId": "12345",
"code": '{"geetest_challenge":"...","geetest_validate":"...","geetest_seccode":"..."}',
"challenge": "...",
"validate": "...",
"seccode": "...",
}Convenience aliases mapped before sending to CapSkip:
| SDK alias | CapSkip API param |
|---|---|
url |
pageurl |
score |
min_score |
minScore |
min_score |
datas |
data-s |
data_s |
data-s |
apiServer |
api_server |
api_subdomain |
api_server |
proxy dict |
proxy + proxytype strings |
proxy = {"type": "HTTPS", "uri": "login:password@1.2.3.4:3128"}Unsupported parameters (e.g. numeric on image captcha, action on v2) raise ValidationException.
Submit without polling. Returns captcha ID string.
captcha_id = solver.send(
method="userrecaptcha",
googlekey="...",
pageurl="https://example.com",
)Poll once. Raises NetworkException while CAPCHA_NOT_READY.
from capskip import NetworkException
code = solver.get_result(captcha_id) # plain text
data = solver.get_result(captcha_id, json=1) # dict when json=1Same API as CapSkip, all methods are async:
from capskip import AsyncCapSkip
solver = AsyncCapSkip()
result = await solver.turnstile(sitekey="...", url="...")| Exception | When |
|---|---|
ValidationException |
Invalid/unsupported parameters |
NetworkException |
Connection error, or captcha not ready |
ApiException |
CapSkip API error response |
TimeoutException |
Polling timeout exceeded |
from capskip import ApiClient
client = ApiClient(host="127.0.0.1", port=8080)
client.in_(method="turnstile", key="capskip", sitekey="...", pageurl="...")
client.res(key="capskip", action="get", id="12345", json=1)