Skip to content
Open
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ jobs:
# to catch packaging errors early. Skipped if lint fails to save CI minutes.
test:
needs: lint
runs-on: ubuntu-latest
# Python 3.9 is not available through setup-python on Ubuntu 26.04,
# which ubuntu-latest currently resolves to. Keep the compatibility
# matrix on an image that provides every supported Python version.
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
Expand Down
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ enabled in your editor, hover over `CaptchaClient`, `AsyncCaptchaClient`, their
methods, or a task class such as `RecaptchaV2TaskProxyless` to read the documentation.
Signature help shows argument names, types, and defaults while you type a call.

The `captcha_solver_api/py.typed` file is intentionally empty. It is the standard
[PEP 561](https://peps.python.org/pep-0561/) marker that tells type checkers to use
the inline type annotations shipped with this package.

Docstrings describe parameters, solution fields, exceptions, and usage examples.
Task examples assume an existing synchronous `client`; with `AsyncCaptchaClient`,
use `solution = await client.solve(task)` instead. Async snippets run inside an
Expand Down Expand Up @@ -151,15 +155,18 @@ on the target page.
|---|---|---|
| reCAPTCHA v2 | ✅ | ✅ |
| reCAPTCHA v2 Enterprise | ✅ | ✅ |
| reCAPTCHA v3 | ✅ | ❌ |
| reCAPTCHA v3 | ✅ | - |
| Cloudflare Turnstile | ✅ | ✅ |
| GeeTest v3 | ✅ | ✅ |
| GeeTest v4 | ✅ | ✅ |
| Image to Text | ✅ | ❌ |
| Image to Text | ✅ | - |
| Yandex SmartCaptcha | ✅ | ✅ |
| Coordinates (click captcha) | ✅ | ❌ |
| Coordinates (click captcha) | ✅ | - |
| Tencent | ✅ | ✅ |

A dash (`-`) means that this CAPTCHA type has no separate task class that accepts
your own proxy; it does not indicate that solving the CAPTCHA is unsupported.

## Client Reference

Every method below is available on both `CaptchaClient` (sync, `requests`-based) and
Expand Down Expand Up @@ -207,14 +214,17 @@ solution -- wraps `create_task()` + `get_task_result()` so you don't poll by han

Returns the `solution` dict once `status` is `"ready"` -- its shape depends on
the task type (see [Captcha Types](#captcha-types)).
Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
Raises `ValidationError` before any request if `task` is not a task object or a
required task parameter is missing or empty. Otherwise raises `ApiError`,
`CaptchaTimeoutError`, or `NetworkError`.

### `create_task(task, language_pool=None)`

Submits `task` and returns its numeric task ID without waiting for a solution.
Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
manage polling yourself (e.g. checking on many tasks from a different process).
Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
Raises `ValidationError` before any request if `task` is invalid (same rules as
`solve()`). Otherwise raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.

### `get_task_result(task_id)`

Expand Down Expand Up @@ -479,7 +489,7 @@ print(result["validate"])
print(result["seccode"])
```
`challenge` is session-specific and expires quickly, so it can't be hardcoded
into a static example -- see [examples/sync/geetest_v3.py](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/sync/geetest_v3.py)
into a static example -- see [examples/sync/geetest_v3_proxyless.py](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/sync/geetest_v3_proxyless.py)
for where the fetch belongs in the flow.

```python
Expand Down Expand Up @@ -722,12 +732,12 @@ sync/async example list, setup steps, expected results, and placeholder guidance
`CAPTCHA_API_KEY` -- they read sample images bundled in
[examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
`python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
- **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
`turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
- **Token captchas** (`recaptcha_v2_*.py`, `recaptcha_v2_enterprise_*.py`, `recaptcha_v3.py`,
`turnstile_*.py`, `yandex_smartcaptcha_*.py`, `geetest_v4_*.py`, `tencent_*.py`) use
placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
`YOUR_CAPTCHA_ID`) -- replace these with the real values from your own target
page before running.
- **`geetest_v3.py`** additionally needs `challenge` fetched fresh for every
- **`geetest_v3_*.py`** additionally needs `challenge` fetched fresh for every
request -- it's single-use and expires within seconds, so it can't be
hardcoded into a static example. `"https://target-site.com/path/to/geetest/init"`
is a placeholder; replace it with a request to your own target's equivalent
Expand Down
36 changes: 36 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Security Policy

## Supported versions

Security fixes are provided for the latest released version of
`captcha-solver-api`. Before reporting a vulnerability, verify that it is still
present after upgrading to the latest version available on PyPI.

| Version | Supported |
|---|---|
| Latest release | Yes |
| Older releases | No |

## Reporting a vulnerability

Please report suspected vulnerabilities privately through
[GitHub Security Advisories](https://github.com/captcha-solver-api/python-sdk/security/advisories/new).
Do not open a public issue or pull request for an undisclosed vulnerability.

Include the following information when possible:

- the affected SDK version and Python version;
- a clear description of the impact;
- steps or a minimal example that reproduces the problem;
- any known mitigations or suggested fixes.

You should receive an acknowledgement within five business days. We will keep
you informed while the report is investigated and will coordinate disclosure
after a fix is available. Please allow a reasonable amount of time for the
issue to be addressed before making it public.

## Scope

This policy covers the Python SDK in this repository. Vulnerabilities in the
Captcha Solver service or website should be reported through the security
channel of the corresponding project.
2 changes: 1 addition & 1 deletion captcha_solver_api/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.4"
__version__ = "1.0.5"
14 changes: 11 additions & 3 deletions captcha_solver_api/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
NetworkError,
ValidationError,
)
from .tasks import BaseTask


class AsyncCaptchaClient:
Expand All @@ -42,7 +43,7 @@ class AsyncCaptchaClient:
the account default. Can be overridden in `solve()` or `create_task()`.

Raises:
ValidationError: `client_key` is empty.
ValidationError: `client_key` is missing, empty, or whitespace.

Example:
from captcha_solver_api import AsyncCaptchaClient
Expand Down Expand Up @@ -84,9 +85,9 @@ def __init__(
pool.

Raises:
ValidationError: `client_key` is empty.
ValidationError: `client_key` is missing, empty, or whitespace.
"""
if not client_key:
if not isinstance(client_key, str) or not client_key.strip():
raise ValidationError("client_key is required")

self.client_key = client_key
Expand Down Expand Up @@ -165,13 +166,18 @@ async def create_task(self, task: Any, language_pool: Optional[str] = None) -> i
The numeric task ID to pass to `get_task_result()`.

Raises:
ValidationError: `task` is not a task object, or a required task
parameter is missing or empty. Raised before any request is sent.
ApiError: The API rejected the task or its parameters.
NetworkError: The request failed at the transport level.
CaptchaTimeoutError: The HTTP request timed out.

Example:
task_id = await client.create_task(task, language_pool="en")
"""
if not isinstance(task, BaseTask):
raise ValidationError("task must be an instance of BaseTask")

payload: Dict[str, Any] = {
"clientKey": self.client_key,
"task": task.to_dict(),
Expand Down Expand Up @@ -273,6 +279,8 @@ async def solve(
or the README's method reference.

Raises:
ValidationError: `task` is not a task object, or a required task
parameter is missing or empty. Raised before any request is sent.
ApiError: The API rejected the task or reported a solving error.
CaptchaTimeoutError: Polling exceeded its deadline or an HTTP request
timed out.
Expand Down
14 changes: 11 additions & 3 deletions captcha_solver_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
NetworkError,
ValidationError,
)
from .tasks import BaseTask


class CaptchaClient:
Expand All @@ -32,7 +33,7 @@ class CaptchaClient:
the account default. Can be overridden in `solve()` or `create_task()`.

Raises:
ValidationError: `client_key` is empty.
ValidationError: `client_key` is missing, empty, or whitespace.

Example:
from captcha_solver_api import CaptchaClient
Expand Down Expand Up @@ -74,9 +75,9 @@ def __init__(
pool.

Raises:
ValidationError: `client_key` is empty.
ValidationError: `client_key` is missing, empty, or whitespace.
"""
if not client_key:
if not isinstance(client_key, str) or not client_key.strip():
raise ValidationError("client_key is required")

self.client_key = client_key
Expand Down Expand Up @@ -153,13 +154,18 @@ def create_task(self, task: Any, language_pool: Optional[str] = None) -> int:
The numeric task ID to pass to `get_task_result()`.

Raises:
ValidationError: `task` is not a task object, or a required task
parameter is missing or empty. Raised before any request is sent.
ApiError: The API rejected the task (bad key, bad parameters, etc).
NetworkError: The request failed at the transport level.
CaptchaTimeoutError: The HTTP request itself timed out (not the solve).

Example:
task_id = client.create_task(task, language_pool="en")
"""
if not isinstance(task, BaseTask):
raise ValidationError("task must be an instance of BaseTask")

payload: Dict[str, Any] = {
"clientKey": self.client_key,
"task": task.to_dict(),
Expand Down Expand Up @@ -251,6 +257,8 @@ def solve(
or the README's method reference.

Raises:
ValidationError: `task` is not a task object, or a required task
parameter is missing or empty. Raised before any request is sent.
ApiError: The API rejected the task or reported an error while solving.
CaptchaTimeoutError: Polling exceeded its deadline or an HTTP request
timed out.
Expand Down
Loading
Loading