capzy 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- capzy/__init__.py +36 -0
- capzy/_version.py +1 -0
- capzy/client.py +224 -0
- capzy/exceptions.py +61 -0
- capzy-0.0.1.dist-info/METADATA +444 -0
- capzy-0.0.1.dist-info/RECORD +8 -0
- capzy-0.0.1.dist-info/WHEEL +4 -0
- capzy-0.0.1.dist-info/licenses/LICENSE +21 -0
capzy/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Capzy — official Python SDK.
|
|
2
|
+
|
|
3
|
+
Quick start:
|
|
4
|
+
|
|
5
|
+
from capzy import CapzyClient
|
|
6
|
+
|
|
7
|
+
capzy = CapzyClient("capzy_xxxxxxxxxxxxxxxxxxxxxxxx")
|
|
8
|
+
|
|
9
|
+
solution = capzy.solve(
|
|
10
|
+
type="AntiTurnstileTaskProxyLess",
|
|
11
|
+
website_url="https://example.com",
|
|
12
|
+
website_key="0x4AAA...",
|
|
13
|
+
)
|
|
14
|
+
print(solution["token"])
|
|
15
|
+
|
|
16
|
+
Find your API key at https://capzy.ai/dashboard.
|
|
17
|
+
Every new account gets $0.10 in free credits — no card required.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from capzy._version import __version__
|
|
21
|
+
from capzy.client import CapzyClient
|
|
22
|
+
from capzy.exceptions import (
|
|
23
|
+
ApiError,
|
|
24
|
+
CapzyError,
|
|
25
|
+
TaskFailedError,
|
|
26
|
+
TaskTimeoutError,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"__version__",
|
|
31
|
+
"CapzyClient",
|
|
32
|
+
"CapzyError",
|
|
33
|
+
"ApiError",
|
|
34
|
+
"TaskFailedError",
|
|
35
|
+
"TaskTimeoutError",
|
|
36
|
+
]
|
capzy/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
capzy/client.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Sync HTTP client for the Capzy API.
|
|
2
|
+
|
|
3
|
+
Most callers want the module-level shortcuts (`capzy.solve(...)`),
|
|
4
|
+
not this class. Use ``CapzyClient`` directly only when you need to
|
|
5
|
+
inject a custom ``requests.Session`` (proxy, retries, custom TLS).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Mapping
|
|
12
|
+
from urllib.parse import urljoin
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from capzy._version import __version__
|
|
17
|
+
from capzy.exceptions import (
|
|
18
|
+
ApiError,
|
|
19
|
+
CapzyError,
|
|
20
|
+
TaskFailedError,
|
|
21
|
+
TaskTimeoutError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
DEFAULT_BASE_URL = "https://api.capzy.ai"
|
|
25
|
+
DEFAULT_TIMEOUT = 30.0 # per-request HTTP timeout
|
|
26
|
+
DEFAULT_POLL_INTERVAL = 2.0 # seconds between getTaskResult polls
|
|
27
|
+
DEFAULT_MAX_WAIT = 180.0 # cap on total polling time per .solve()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# snake_case → camelCase wire-name remap. Anything not listed passes
|
|
31
|
+
# through untouched, so you can also just write camelCase directly.
|
|
32
|
+
_KEY_RENAME = {
|
|
33
|
+
"website_url": "websiteURL",
|
|
34
|
+
"website_key": "websiteKey",
|
|
35
|
+
"website_public_key": "websitePublicKey",
|
|
36
|
+
"is_invisible": "isInvisible",
|
|
37
|
+
"is_enterprise": "isEnterprise",
|
|
38
|
+
"page_action": "pageAction",
|
|
39
|
+
"min_score": "minScore",
|
|
40
|
+
"api_domain": "apiDomain",
|
|
41
|
+
"enterprise_payload": "enterprisePayload",
|
|
42
|
+
"data_s": "data-s",
|
|
43
|
+
"user_agent": "userAgent",
|
|
44
|
+
"proxy_address": "proxyAddress",
|
|
45
|
+
"proxy_port": "proxyPort",
|
|
46
|
+
"proxy_login": "proxyLogin",
|
|
47
|
+
"proxy_password": "proxyPassword",
|
|
48
|
+
"proxy_type": "proxyType",
|
|
49
|
+
"captcha_id": "captchaId",
|
|
50
|
+
"captcha_url": "captchaUrl",
|
|
51
|
+
"geetest_api_server_subdomain": "geetestApiServerSubdomain",
|
|
52
|
+
"geetest_get_lib": "geetestGetLib",
|
|
53
|
+
"funcaptcha_api_js_subdomain": "funcaptchaApiJSSubdomain",
|
|
54
|
+
"case_sensitive": "case",
|
|
55
|
+
"min_length": "minLength",
|
|
56
|
+
"max_length": "maxLength",
|
|
57
|
+
"app_id": "appId",
|
|
58
|
+
"validate_id": "validateId",
|
|
59
|
+
"aws_key": "awsKey",
|
|
60
|
+
"aws_iv": "awsIv",
|
|
61
|
+
"aws_context": "awsContext",
|
|
62
|
+
"aws_challenge_js": "awsChallengeJS",
|
|
63
|
+
"package_name": "packageName",
|
|
64
|
+
"device_id": "deviceId",
|
|
65
|
+
"device_name": "deviceName",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _camelize_params(params: Mapping[str, Any]) -> dict[str, Any]:
|
|
70
|
+
"""Pass-through with snake_case keys auto-converted, None values dropped."""
|
|
71
|
+
out: dict[str, Any] = {}
|
|
72
|
+
for k, v in params.items():
|
|
73
|
+
if v is None:
|
|
74
|
+
continue
|
|
75
|
+
out[_KEY_RENAME.get(k, k)] = v
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CapzyClient:
|
|
80
|
+
"""Lower-level handle. Most callers use the module-level functions.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
api_key: API key from https://capzy.ai/dashboard
|
|
84
|
+
base_url: override the API root (defaults to api.capzy.ai)
|
|
85
|
+
timeout: per-request HTTP timeout in seconds
|
|
86
|
+
session: inject a custom ``requests.Session``
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
api_key: str,
|
|
92
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
93
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
94
|
+
session: requests.Session | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
if not api_key:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"api_key is required (get one at https://capzy.ai/dashboard)"
|
|
99
|
+
)
|
|
100
|
+
self.api_key = api_key
|
|
101
|
+
self.base_url = base_url.rstrip("/") + "/"
|
|
102
|
+
self.timeout = timeout
|
|
103
|
+
self._session = session or requests.Session()
|
|
104
|
+
self._session.headers.setdefault("User-Agent", f"capzy-python/{__version__}")
|
|
105
|
+
self._session.headers.setdefault("Accept", "application/json")
|
|
106
|
+
|
|
107
|
+
# ── Public methods ──────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
def get_balance(self) -> float:
|
|
110
|
+
body = self._post("getBalance", {"clientKey": self.api_key})
|
|
111
|
+
return float(body.get("balance", 0.0))
|
|
112
|
+
|
|
113
|
+
def create_task(self, *, type: str, **params: Any) -> dict[str, Any]:
|
|
114
|
+
"""Submit a task — returns the raw createTask response."""
|
|
115
|
+
task_body = {"type": type, **_camelize_params(params)}
|
|
116
|
+
return self._post(
|
|
117
|
+
"createTask",
|
|
118
|
+
{"clientKey": self.api_key, "task": task_body},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def get_task_result(self, task_id: str) -> dict[str, Any]:
|
|
122
|
+
return self._post(
|
|
123
|
+
"getTaskResult",
|
|
124
|
+
{"clientKey": self.api_key, "taskId": task_id},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def report_score(
|
|
128
|
+
self,
|
|
129
|
+
task_id: str,
|
|
130
|
+
score: float,
|
|
131
|
+
action: str | None = None,
|
|
132
|
+
hostname: str | None = None,
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Report a reCAPTCHA v3 score for dashboard analytics."""
|
|
135
|
+
self._post(
|
|
136
|
+
"reportScore",
|
|
137
|
+
{
|
|
138
|
+
"clientKey": self.api_key,
|
|
139
|
+
"taskId": task_id,
|
|
140
|
+
"score": score,
|
|
141
|
+
"action": action,
|
|
142
|
+
"hostname": hostname,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def solve(
|
|
147
|
+
self,
|
|
148
|
+
*,
|
|
149
|
+
type: str,
|
|
150
|
+
poll_interval: float = DEFAULT_POLL_INTERVAL,
|
|
151
|
+
max_wait: float = DEFAULT_MAX_WAIT,
|
|
152
|
+
**params: Any,
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
"""Submit + poll until ready. Returns the ``solution`` dict.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ApiError: createTask was rejected (e.g. bad key, wrong type).
|
|
158
|
+
TaskFailedError: the task ran but failed (refunded).
|
|
159
|
+
TaskTimeoutError: ``max_wait`` elapsed (refunded).
|
|
160
|
+
"""
|
|
161
|
+
created = self.create_task(type=type, **params)
|
|
162
|
+
|
|
163
|
+
# ImageToText / MathCaptcha return the solution synchronously.
|
|
164
|
+
if created.get("solution"):
|
|
165
|
+
return created["solution"]
|
|
166
|
+
|
|
167
|
+
task_id = created.get("taskId")
|
|
168
|
+
if not task_id:
|
|
169
|
+
raise CapzyError("createTask did not return a taskId")
|
|
170
|
+
|
|
171
|
+
server_timeout = created.get("timeout")
|
|
172
|
+
if isinstance(server_timeout, (int, float)) and server_timeout > 0:
|
|
173
|
+
max_wait = min(max_wait, float(server_timeout) + 5.0)
|
|
174
|
+
|
|
175
|
+
deadline = time.monotonic() + max_wait
|
|
176
|
+
while True:
|
|
177
|
+
time.sleep(poll_interval)
|
|
178
|
+
result = self.get_task_result(task_id)
|
|
179
|
+
status = result.get("status")
|
|
180
|
+
if status == "ready":
|
|
181
|
+
return result.get("solution") or {}
|
|
182
|
+
if status == "failed":
|
|
183
|
+
raise TaskFailedError(
|
|
184
|
+
task_id,
|
|
185
|
+
result.get("errorCode"),
|
|
186
|
+
result.get("errorDescription"),
|
|
187
|
+
)
|
|
188
|
+
if time.monotonic() >= deadline:
|
|
189
|
+
raise TaskTimeoutError(task_id, max_wait)
|
|
190
|
+
|
|
191
|
+
# ── Internals ───────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def _post(self, path: str, body: Mapping[str, Any]) -> dict[str, Any]:
|
|
194
|
+
url = urljoin(self.base_url, path)
|
|
195
|
+
clean = {k: v for k, v in body.items() if v is not None}
|
|
196
|
+
try:
|
|
197
|
+
resp = self._session.post(url, json=clean, timeout=self.timeout)
|
|
198
|
+
except requests.RequestException as exc:
|
|
199
|
+
raise CapzyError(f"network error talking to {url}: {exc}") from exc
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
data = resp.json()
|
|
203
|
+
except ValueError as exc:
|
|
204
|
+
raise CapzyError(
|
|
205
|
+
f"expected JSON from {url} (HTTP {resp.status_code}): "
|
|
206
|
+
f"{resp.text[:200]}"
|
|
207
|
+
) from exc
|
|
208
|
+
|
|
209
|
+
if not isinstance(data, dict):
|
|
210
|
+
raise CapzyError(f"unexpected response shape from {url}: {data!r}")
|
|
211
|
+
|
|
212
|
+
error_id = data.get("errorId", 0) or 0
|
|
213
|
+
if error_id != 0:
|
|
214
|
+
raise ApiError(
|
|
215
|
+
error_id=int(error_id),
|
|
216
|
+
error_code=data.get("errorCode"),
|
|
217
|
+
error_description=data.get("errorDescription"),
|
|
218
|
+
recommended_task_type=data.get("recommendedTaskType"),
|
|
219
|
+
raw=data,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if resp.status_code >= 400:
|
|
223
|
+
raise CapzyError(f"HTTP {resp.status_code} from {url}: {resp.text[:200]}")
|
|
224
|
+
return data
|
capzy/exceptions.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Exceptions raised by the Capzy SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CapzyError(Exception):
|
|
9
|
+
"""Base class for every error raised by this SDK."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiError(CapzyError):
|
|
13
|
+
"""The API returned a non-zero errorId.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
error_id: numeric errorId from the response.
|
|
17
|
+
error_code: short string code (e.g. "ERROR_KEY_DOES_NOT_EXIST").
|
|
18
|
+
error_description: human-readable explanation.
|
|
19
|
+
recommended_task_type: set when the API hints the caller picked
|
|
20
|
+
the wrong task type for the sitekey.
|
|
21
|
+
raw: full decoded JSON body, for debugging.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
error_id: int,
|
|
27
|
+
error_code: str | None,
|
|
28
|
+
error_description: str | None,
|
|
29
|
+
recommended_task_type: str | None = None,
|
|
30
|
+
raw: dict[str, Any] | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.error_id = error_id
|
|
33
|
+
self.error_code = error_code
|
|
34
|
+
self.error_description = error_description
|
|
35
|
+
self.recommended_task_type = recommended_task_type
|
|
36
|
+
self.raw = raw or {}
|
|
37
|
+
msg = f"[{error_code or error_id}] {error_description or 'API error'}"
|
|
38
|
+
if recommended_task_type:
|
|
39
|
+
msg += f" (hint: try {recommended_task_type})"
|
|
40
|
+
super().__init__(msg)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TaskFailedError(CapzyError):
|
|
44
|
+
"""getTaskResult returned status='failed'."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, task_id: str, error_code: str | None, error_description: str | None) -> None:
|
|
47
|
+
self.task_id = task_id
|
|
48
|
+
self.error_code = error_code
|
|
49
|
+
self.error_description = error_description
|
|
50
|
+
super().__init__(
|
|
51
|
+
f"Task {task_id} failed: [{error_code or '?'}] {error_description or 'no description'}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TaskTimeoutError(CapzyError):
|
|
56
|
+
"""The task did not return a solution before the polling deadline."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, task_id: str, waited: float) -> None:
|
|
59
|
+
self.task_id = task_id
|
|
60
|
+
self.waited = waited
|
|
61
|
+
super().__init__(f"Task {task_id} did not finish within {waited:.0f}s")
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: capzy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Official Python SDK for the Capzy captcha-solving API
|
|
5
|
+
Project-URL: Homepage, https://capzy.ai
|
|
6
|
+
Project-URL: Documentation, https://capzy.ai/docs
|
|
7
|
+
Project-URL: Source, https://github.com/capzy/capzy-pip
|
|
8
|
+
Project-URL: Issues, https://github.com/capzy/capzy-pip/issues
|
|
9
|
+
Author-email: Capzy <support@capzy.ai>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: aws-waf,captcha,captcha-solver,capzy,cloudflare,datadome,funcaptcha,geetest,recaptcha,turnstile
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: requests>=2.28
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
30
|
+
Requires-Dist: responses>=0.24; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
<img src="https://capzy.ai/capzy-logo.svg" alt="Capzy" width="220" />
|
|
36
|
+
|
|
37
|
+
# `capzy` — official Python SDK
|
|
38
|
+
|
|
39
|
+
**Solve every major captcha with a single line of Python.**
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/capzy/)
|
|
42
|
+
[](https://pypi.org/project/capzy/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[](https://capzy.ai/docs)
|
|
45
|
+
[](https://capzy.ai)
|
|
46
|
+
|
|
47
|
+
[Website](https://capzy.ai) · [Dashboard](https://capzy.ai/dashboard) · [Docs](https://capzy.ai/docs) · [Pricing](https://capzy.ai/pricing) · [Get free credits](https://capzy.ai/auth/register)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Why Capzy
|
|
54
|
+
|
|
55
|
+
> Built by people who were tired of paying half a cent to get back `errorCode: ERROR_NO_AVAILABLE_WORKERS`.
|
|
56
|
+
|
|
57
|
+
- ⚡️ **Fast.** Median solve <5s for Turnstile / reCAPTCHA v2 / hCaptcha.
|
|
58
|
+
- 💸 **Cheap.** Token-bound captchas start at **$0.40 per 1,000 solves**.
|
|
59
|
+
- 🧠 **Solves what others don't.** Native support for **Temu**, **Shopee** (slider + curve), **Binance bCAPTCHA2**, **CaptchaFox**, **MTCaptcha**, **Lemin**, **ALTCHA**, **Yidun**, **Capy** — not just the obvious ones. *(hCaptcha is in active development and not currently sold; status: see [roadmap](#roadmap).)*
|
|
60
|
+
- 🧩 **Drop-in compatible.** Uses the same `createTask` / `getTaskResult` shape your existing scripts already speak. Migration is a one-line `base_url` change.
|
|
61
|
+
- 🆓 **Free credits on sign-up.** Every new account gets **$0.10** in real credits to test in production. No card required.
|
|
62
|
+
- 🔓 **No retainer, no minimum.** Pay-as-you-go. Top up when you want.
|
|
63
|
+
- 📊 **Real dashboard.** Per-key analytics, score quality charts for v3, full payment audit log.
|
|
64
|
+
|
|
65
|
+
> [**Create a free account →**](https://capzy.ai/auth/register)
|
|
66
|
+
> Get **$0.10 in free credits** the moment you sign up — enough to verify integration end-to-end on roughly **80 reCAPTCHA v3** or **80 Turnstile** solves before you pay a cent.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install capzy
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Requires Python ≥ 3.9 and `requests`. That's it.
|
|
77
|
+
|
|
78
|
+
> ⚠️ While we wait for our PyPI listing to be approved, you can install straight from the repo:
|
|
79
|
+
>
|
|
80
|
+
> ```bash
|
|
81
|
+
> pip install "git+https://github.com/capzy/capzy-pip.git"
|
|
82
|
+
> ```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 60-second quickstart
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from capzy import CapzyClient
|
|
90
|
+
|
|
91
|
+
capzy = CapzyClient("capzy_xxxxxxxxxxxxxxxxxxxxxxxx") # from capzy.ai/dashboard
|
|
92
|
+
|
|
93
|
+
solution = capzy.solve(
|
|
94
|
+
type="AntiTurnstileTaskProxyLess",
|
|
95
|
+
website_url="https://example.com/login",
|
|
96
|
+
website_key="0x4AAAAAAA...",
|
|
97
|
+
)
|
|
98
|
+
print(solution["token"]) # paste into the cf-turnstile-response field
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
That's the whole pattern. `solve()` does `createTask` → polls `getTaskResult` → hands you the solution dict.
|
|
102
|
+
|
|
103
|
+
Need to route the solve through your own proxy? Switch to the `…Task` (non-`ProxyLess`) type and add the proxy params:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
solution = capzy.solve(
|
|
107
|
+
type="AntiTurnstileTask",
|
|
108
|
+
website_url="https://example.com/login",
|
|
109
|
+
website_key="0x4AAAAAAA...",
|
|
110
|
+
proxy_type="http",
|
|
111
|
+
proxy_address="123.45.67.89",
|
|
112
|
+
proxy_port=8080,
|
|
113
|
+
proxy_login="user",
|
|
114
|
+
proxy_password="pass",
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Need lower-level control? Use the raw verbs:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
created = capzy.create_task(
|
|
122
|
+
type="AntiTurnstileTaskProxyLess",
|
|
123
|
+
website_url="https://example.com/login",
|
|
124
|
+
website_key="0x4AAAAAAA...",
|
|
125
|
+
)
|
|
126
|
+
task_id = created["taskId"]
|
|
127
|
+
|
|
128
|
+
# Poll on your own schedule:
|
|
129
|
+
status = capzy.get_task_result(task_id)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> **Param naming.** Pass either `website_url=` / `website_key=` (Python style) or
|
|
133
|
+
> `websiteURL=` / `websiteKey=` (wire style). They're equivalent — the SDK
|
|
134
|
+
> normalises snake_case to the camelCase the API expects.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## How it works
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
┌──────────────┐ createTask ┌──────────┐ enqueue ┌──────────┐
|
|
142
|
+
│ your script │ ────────────▶ │ Capzy │ ────────────▶ │ solver │
|
|
143
|
+
│ (capzy SDK) │ ◀──────────── │ API │ ◀──────────── │ cluster │
|
|
144
|
+
└──────────────┘ getTaskResult └──────────┘ solution └──────────┘
|
|
145
|
+
(polls until "ready")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Submit → poll → done. No webhooks to set up, no SDK runtime required on the solver side, no captive browser to manage.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Supported captchas
|
|
153
|
+
|
|
154
|
+
Every captcha below is supported as a **proxyless** task (we handle the upstream routing) and a **proxy** task (you supply the upstream IP). All prices are USD per solve.
|
|
155
|
+
|
|
156
|
+
### Token-based — the workhorses
|
|
157
|
+
|
|
158
|
+
| Captcha | Task type | Price | Notes |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| Cloudflare Turnstile | `AntiTurnstileTaskProxyLess` | **$0.0012** | Including invisible variant |
|
|
161
|
+
| Cloudflare Challenge | `AntiCloudflareTask` | $0.005 | Full JS-challenge page |
|
|
162
|
+
| reCAPTCHA v2 | `ReCaptchaV2TaskProxyLess` | **$0.002** | Checkbox + invisible |
|
|
163
|
+
| reCAPTCHA v2 Enterprise | `ReCaptchaV2EnterpriseTaskProxyLess` | $0.004 | |
|
|
164
|
+
| reCAPTCHA v3 | `ReCaptchaV3TaskProxyLess` | **$0.0015** | Action + min-score |
|
|
165
|
+
| reCAPTCHA v3 Enterprise | `ReCaptchaV3EnterpriseTaskProxyLess` | $0.005 | |
|
|
166
|
+
| FunCaptcha (Arkose) | `FunCaptchaTaskProxyLess` | $0.004 | Including Roblox flow |
|
|
167
|
+
| GeeTest v4 | `GeeTestTaskProxyLess` | **$0.003** | captchaId-based |
|
|
168
|
+
| GeeTest v3 | `GeeTestV3TaskProxyLess` | $0.0035 | gt + challenge |
|
|
169
|
+
| CaptchaFox | `CaptchaFoxTaskProxyLess` | **$0.0012** | |
|
|
170
|
+
| MTCaptcha | `MtCaptchaTaskProxyLess` | $0.002 | |
|
|
171
|
+
| Friendly Captcha | `FriendlyCaptchaTaskProxyLess` | $0.002 | |
|
|
172
|
+
| ALTCHA | `AltchaTaskProxyLess` | $0.001 | Pure proof-of-work |
|
|
173
|
+
| Lemin | `LeminTaskProxyLess` | $0.0015 | |
|
|
174
|
+
| Capy Puzzle | `CapyTaskProxyLess` | $0.002 | |
|
|
175
|
+
| NetEase Yidun | `YidunSliderTaskProxyLess` | $0.003 | |
|
|
176
|
+
| Yandex SmartCaptcha | `YandexSmartCaptchaTaskProxyLess` | $0.025 | |
|
|
177
|
+
| Binance bCAPTCHA2 | `BinanceCaptchaTask` | $0.003 | |
|
|
178
|
+
| Tencent | `TencentTaskProxyLess` | $0.002 | |
|
|
179
|
+
| Shopee Slider | `ShopeeSliderTaskProxyLess` | $0.002 | |
|
|
180
|
+
| Shopee Curve Slider | `ShopeeCurveTaskProxyLess` | $0.003 | |
|
|
181
|
+
| Temu | `TemuCaptchaTaskProxyLess` | $0.002 | |
|
|
182
|
+
| Alibaba | `AntiAlibabaCaptchaTaskProxyLess` | $0.003 | |
|
|
183
|
+
|
|
184
|
+
### Anti-bot platforms
|
|
185
|
+
|
|
186
|
+
| Platform | Task type | Price | Notes |
|
|
187
|
+
|---|---|---|---|
|
|
188
|
+
| AWS WAF (full challenge) | `AntiAwsWafTaskProxyLess` | $0.012 | |
|
|
189
|
+
| AWS WAF (image classify) | `AwsWafClassification` | **$0.0008** | Just the image piece |
|
|
190
|
+
| DataDome Slider | `DataDomeSliderTask` | $0.04 | Proxy required |
|
|
191
|
+
| PerimeterX | `AntiPerimeterXTaskProxyLess` | $0.04 | |
|
|
192
|
+
| Imperva Incapsula | `AntiImpervaTaskProxyLess` | $0.04 | |
|
|
193
|
+
| Akamai Bot Manager | `AntiAkamaiBMPTaskProxyLess` | $0.05 | Mobile (BMP) |
|
|
194
|
+
| Kasada | `KasadaCaptchaTaskProxyLess` | $0.005 | |
|
|
195
|
+
|
|
196
|
+
### Image-based
|
|
197
|
+
|
|
198
|
+
| Type | Task type | Price | Use case |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| Image-to-Text | `ImageToTextTask` | **$0.001** | Generic OCR captchas |
|
|
201
|
+
| Math Captcha | `MathCaptchaTask` | $0.001 | "What is 7 + 3?" |
|
|
202
|
+
| Coordinates Click | `CoordinatesTask` | $0.001 | "Click the cat" |
|
|
203
|
+
| Rotate | `RotateTask` | $0.001 | Rotate-to-upright |
|
|
204
|
+
|
|
205
|
+
Full live pricing at **[capzy.ai/pricing](https://capzy.ai/pricing)** — these tables are scraped from the same source-of-truth.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Examples per service
|
|
210
|
+
|
|
211
|
+
### Cloudflare Turnstile
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from capzy import CapzyClient
|
|
215
|
+
|
|
216
|
+
capzy = CapzyClient("capzy_xxx")
|
|
217
|
+
sol = capzy.solve(
|
|
218
|
+
type="AntiTurnstileTaskProxyLess",
|
|
219
|
+
website_url="https://example.com",
|
|
220
|
+
website_key="0x4AAAAAAA...",
|
|
221
|
+
action="login", # optional — must match data-action
|
|
222
|
+
cdata="user-123", # optional — server-issued cdata
|
|
223
|
+
)
|
|
224
|
+
print(sol["token"])
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### reCAPTCHA v2
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from capzy import CapzyClient
|
|
231
|
+
|
|
232
|
+
capzy = CapzyClient("capzy_xxx")
|
|
233
|
+
sol = capzy.solve(
|
|
234
|
+
type="ReCaptchaV2TaskProxyLess",
|
|
235
|
+
website_url="https://example.com",
|
|
236
|
+
website_key="6Lc_aCMTAAAAAB...",
|
|
237
|
+
is_invisible=False,
|
|
238
|
+
)
|
|
239
|
+
print(sol["gRecaptchaResponse"])
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### reCAPTCHA v3 (score-based)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from capzy import CapzyClient
|
|
246
|
+
|
|
247
|
+
capzy = CapzyClient("capzy_xxx")
|
|
248
|
+
created = capzy.create_task(
|
|
249
|
+
type="ReCaptchaV3TaskProxyLess",
|
|
250
|
+
website_url="https://example.com",
|
|
251
|
+
website_key="6Lc_aCMTAAAAAB...",
|
|
252
|
+
page_action="checkout",
|
|
253
|
+
min_score=0.7,
|
|
254
|
+
)
|
|
255
|
+
task_id = created["taskId"]
|
|
256
|
+
|
|
257
|
+
# After you call Google siteverify with your secret, report the score back —
|
|
258
|
+
# it lights up your v3 quality dashboard and helps us auto-route your traffic.
|
|
259
|
+
capzy.report_score(task_id, score=0.9, action="checkout", hostname="example.com")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### GeeTest v4
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from capzy import CapzyClient
|
|
266
|
+
|
|
267
|
+
capzy = CapzyClient("capzy_xxx")
|
|
268
|
+
sol = capzy.solve(
|
|
269
|
+
type="GeeTestV4TaskProxyLess",
|
|
270
|
+
website_url="https://example.com",
|
|
271
|
+
captcha_id="abc123...",
|
|
272
|
+
)
|
|
273
|
+
# sol contains captcha_id, lot_number, pass_token, gen_time, captcha_output
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Image-to-Text (sync solve — no polling)
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
import base64
|
|
280
|
+
from capzy import CapzyClient
|
|
281
|
+
|
|
282
|
+
capzy = CapzyClient("capzy_xxx")
|
|
283
|
+
with open("captcha.png", "rb") as f:
|
|
284
|
+
body = base64.b64encode(f.read()).decode()
|
|
285
|
+
|
|
286
|
+
sol = capzy.solve(type="ImageToTextTask", body=body, case="lower")
|
|
287
|
+
print(sol["text"])
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
More copy-pasteable runners in [`examples/`](./examples).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Free credits & getting an API key
|
|
295
|
+
|
|
296
|
+
1. Create an account at **[capzy.ai/auth/register](https://capzy.ai/auth/register)**.
|
|
297
|
+
2. We post a **$0.10 welcome bonus** as a real credit transaction on your dashboard. (Audit it like any other payment.)
|
|
298
|
+
3. Generate an API key on **[capzy.ai/dashboard](https://capzy.ai/dashboard)** and drop it into `CapzyClient("capzy_…")`.
|
|
299
|
+
|
|
300
|
+
The $0.10 is plenty to verify end-to-end:
|
|
301
|
+
|
|
302
|
+
| Captcha | Solves you can run on $0.10 |
|
|
303
|
+
|---|---|
|
|
304
|
+
| reCAPTCHA v3 | ~66 |
|
|
305
|
+
| Turnstile | ~83 |
|
|
306
|
+
| reCAPTCHA v2 | ~50 |
|
|
307
|
+
| Image-to-Text | ~100 |
|
|
308
|
+
| GeeTest v4 | ~33 |
|
|
309
|
+
|
|
310
|
+
When you run out, top up with **Stripe** (card / Apple Pay / Google Pay) or **MixPay** (USDT / BTC / 30+ coins). No subscription, no minimum.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Handling errors
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from capzy import CapzyClient, ApiError, TaskFailedError, TaskTimeoutError
|
|
318
|
+
|
|
319
|
+
capzy = CapzyClient("capzy_xxx")
|
|
320
|
+
try:
|
|
321
|
+
sol = capzy.solve(
|
|
322
|
+
type="AntiTurnstileTaskProxyLess",
|
|
323
|
+
website_url="https://example.com",
|
|
324
|
+
website_key="0x4AAAAAAA...",
|
|
325
|
+
)
|
|
326
|
+
except ApiError as e:
|
|
327
|
+
# createTask was rejected — bad key, wrong task type, insufficient funds, etc.
|
|
328
|
+
print("API rejected the task:", e.error_code, e.error_description)
|
|
329
|
+
if e.recommended_task_type:
|
|
330
|
+
print("Hint — submit as:", e.recommended_task_type)
|
|
331
|
+
except TaskFailedError as e:
|
|
332
|
+
# Task ran but couldn't be solved. Refunded automatically.
|
|
333
|
+
print("Solver failed:", e.error_code, e.error_description)
|
|
334
|
+
except TaskTimeoutError as e:
|
|
335
|
+
# We didn't get an answer within max_wait. Refunded automatically.
|
|
336
|
+
print(f"No answer after {e.waited:.0f}s")
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**You are never charged for failed or timed-out tasks.** Refunds post as real credit transactions, visible in your dashboard.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Configuration
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
capzy = CapzyClient(
|
|
347
|
+
api_key="capzy_xxx",
|
|
348
|
+
base_url="https://api.capzy.ai", # override for on-prem / staging
|
|
349
|
+
timeout=30.0, # per HTTP-request timeout
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Tune the polling behaviour per .solve() call:
|
|
353
|
+
capzy.solve(type="AntiTurnstileTaskProxyLess", website_url=..., website_key=...,
|
|
354
|
+
poll_interval=1.0, max_wait=90.0)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Need a proxy on the SDK's own egress, retries, or custom TLS? Pass your own `requests.Session`:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
import requests
|
|
361
|
+
from capzy import CapzyClient
|
|
362
|
+
|
|
363
|
+
session = requests.Session()
|
|
364
|
+
session.proxies = {"https": "http://user:pass@proxy.example:8080"}
|
|
365
|
+
capzy = CapzyClient("capzy_xxx", session=session)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## API reference
|
|
371
|
+
|
|
372
|
+
`CapzyClient` is a thin wrapper around the Capzy HTTP API:
|
|
373
|
+
|
|
374
|
+
| Method | Endpoint | Returns |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| `capzy.solve(type=..., **params)` | `POST /createTask` + polls `POST /getTaskResult` | the `solution` dict |
|
|
377
|
+
| `capzy.create_task(type=..., **params)` | `POST /createTask` | `{taskId, status?, solution?, timeout?}` |
|
|
378
|
+
| `capzy.get_task_result(task_id)` | `POST /getTaskResult` | `{status, solution?, cost?, ip?, …}` |
|
|
379
|
+
| `capzy.report_score(task_id, score, action=None, hostname=None)` | `POST /reportScore` | `None` |
|
|
380
|
+
| `capzy.get_balance()` | `POST /getBalance` | `float` USD |
|
|
381
|
+
|
|
382
|
+
All task parameters travel as kwargs. Snake_case (`website_url`, `proxy_address`, `min_score`) and camelCase (`websiteURL`, `proxyAddress`, `minScore`) are equivalent — the SDK normalises to the wire format.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## FAQ
|
|
387
|
+
|
|
388
|
+
**Do you need to supply a proxy?**
|
|
389
|
+
No — every `*ProxyLess` task type runs against our own network. Bring your own only when the target binds the token to your egress IP (some anti-bot platforms do).
|
|
390
|
+
|
|
391
|
+
**Do you support async?**
|
|
392
|
+
Not in v0.0.1. The sync client is intentionally tiny — pair it with a thread pool, `anyio.to_thread`, or `asyncio.to_thread` if you need fan-out today. A native `AsyncCapzyClient` is on the roadmap.
|
|
393
|
+
|
|
394
|
+
**What happens if a task fails?**
|
|
395
|
+
The credit is automatically refunded as a `refund` transaction. The SDK raises `TaskFailedError` so you can retry with a different task type or proxy.
|
|
396
|
+
|
|
397
|
+
**Can I run this in a long-running service?**
|
|
398
|
+
Yes. Re-use one `CapzyClient` across threads — the underlying `requests.Session` is thread-safe for our usage pattern.
|
|
399
|
+
|
|
400
|
+
**Where do I find my API key?**
|
|
401
|
+
[capzy.ai/dashboard](https://capzy.ai/dashboard) → API keys → New key.
|
|
402
|
+
|
|
403
|
+
**Do you have rate limits?**
|
|
404
|
+
Per-key concurrency limits exist (visible on your dashboard). When you hit them, `createTask` returns `ERROR_RATE_LIMIT` and the SDK raises `ApiError(error_code="ERROR_RATE_LIMIT")` so you can back off.
|
|
405
|
+
|
|
406
|
+
**Where do solutions need to be submitted?**
|
|
407
|
+
- Turnstile → `cf-turnstile-response` form field
|
|
408
|
+
- reCAPTCHA v2 / Enterprise → `g-recaptcha-response`
|
|
409
|
+
- Others → service-specific; see the per-service guides at [capzy.ai/docs](https://capzy.ai/docs)
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Roadmap
|
|
414
|
+
|
|
415
|
+
Captchas not yet on the price list but actively being worked on:
|
|
416
|
+
|
|
417
|
+
- **hCaptcha** (incl. Enterprise) — local CV pipeline in late-stage testing; SDK task types (`HCaptchaTaskProxyLess`, etc.) already accepted so your code is forward-compatible.
|
|
418
|
+
- **Native `AsyncCapzyClient`** — `asyncio`-native client, same API surface.
|
|
419
|
+
- **Streaming `solveMany()`** — fan out N tasks, yield as they finish.
|
|
420
|
+
|
|
421
|
+
Want something prioritised? Open a [GitHub issue](https://github.com/capzy/capzy-pip/issues) or email **support@capzy.ai**.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Links
|
|
426
|
+
|
|
427
|
+
- 🌐 **Website:** https://capzy.ai
|
|
428
|
+
- 📚 **Documentation:** https://capzy.ai/docs
|
|
429
|
+
- 💵 **Pricing:** https://capzy.ai/pricing
|
|
430
|
+
- 🔑 **Dashboard / API keys:** https://capzy.ai/dashboard
|
|
431
|
+
- 🆓 **Free credits on sign-up:** https://capzy.ai/auth/register
|
|
432
|
+
- 📜 **Changelog:** [CHANGELOG.md](CHANGELOG.md)
|
|
433
|
+
- 🐛 **Issues:** https://github.com/capzy/capzy-pip/issues
|
|
434
|
+
- 💬 **Support:** support@capzy.ai
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## License
|
|
439
|
+
|
|
440
|
+
MIT — see [LICENSE](LICENSE). Use it in proprietary software, redistribute it, fork it. Just don't blame us if your bot finds love.
|
|
441
|
+
|
|
442
|
+
<div align="center">
|
|
443
|
+
<sub>Built with care by the Capzy team — <a href="https://capzy.ai">capzy.ai</a></sub>
|
|
444
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
capzy/__init__.py,sha256=yzxYssWh4htlPe4VENqooceMn7_pgS2f3inxzMgqwM8,763
|
|
2
|
+
capzy/_version.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
|
3
|
+
capzy/client.py,sha256=I8DLIHttAhoGDcgkzG0l-rgJ0NKJmpxwQLk5ViiOhpg,7857
|
|
4
|
+
capzy/exceptions.py,sha256=LXcGltxLxZI_yUt19lI0VU11gB_FAPahewLVpg9c13I,2047
|
|
5
|
+
capzy-0.0.1.dist-info/METADATA,sha256=hyFFK-jXENicO39t6k3PtiijSCtDX7w5KR7aBGUngZA,16670
|
|
6
|
+
capzy-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
capzy-0.0.1.dist-info/licenses/LICENSE,sha256=GQcN_EQWmsYjtb7l_HCKJaTusmtaPe7i3KAr3P7lnLw,1062
|
|
8
|
+
capzy-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Capzy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|