captcha-solver-api 1.0.4__tar.gz → 1.1.0__tar.gz

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.
Files changed (19) hide show
  1. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/PKG-INFO +91 -27
  2. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/README.md +87 -23
  3. captcha_solver_api-1.1.0/captcha_solver_api/_version.py +1 -0
  4. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/async_client.py +47 -8
  5. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/client.py +47 -8
  6. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/exceptions.py +26 -2
  7. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/tasks.py +230 -135
  8. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api.egg-info/PKG-INFO +91 -27
  9. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api.egg-info/requires.txt +2 -2
  10. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/pyproject.toml +3 -3
  11. captcha_solver_api-1.0.4/captcha_solver_api/_version.py +0 -1
  12. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/LICENSE.md +0 -0
  13. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/__init__.py +0 -0
  14. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api/py.typed +0 -0
  15. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api.egg-info/SOURCES.txt +0 -0
  16. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api.egg-info/dependency_links.txt +0 -0
  17. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/captcha_solver_api.egg-info/top_level.txt +0 -0
  18. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/setup.cfg +0 -0
  19. {captcha_solver_api-1.0.4 → captcha_solver_api-1.1.0}/tests/test_polling.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: captcha-solver-api
3
- Version: 1.0.4
3
+ Version: 1.1.0
4
4
  Summary: Official Python SDK for solving reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3/v4, Yandex SmartCaptcha, Tencent, and image/click captchas via the Captcha Solver API
5
5
  Author: Captcha Solver Team
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://captcha-solver.com
8
- Project-URL: Documentation, https://captcha-solver.com/en/docs/captcha-types
8
+ Project-URL: Documentation, https://github.com/captcha-solver-api/python-sdk#readme
9
9
  Project-URL: Repository, https://github.com/captcha-solver-api/python-sdk
10
10
  Keywords: captcha,captcha solver,recaptcha,recaptcha v2,recaptcha v3,cloudflare turnstile,geetest,geetest v4,yandex smartcaptcha,tencent captcha,image captcha,coordinates,click captcha,api
11
11
  Classifier: Development Status :: 5 - Production/Stable
@@ -25,8 +25,8 @@ Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.9
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE.md
28
- Requires-Dist: requests>=2.28
29
- Requires-Dist: httpx>=0.24
28
+ Requires-Dist: requests>=2.32.4
29
+ Requires-Dist: httpx>=0.27
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: pytest>=7; extra == "dev"
32
32
  Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
@@ -115,6 +115,10 @@ enabled in your editor, hover over `CaptchaClient`, `AsyncCaptchaClient`, their
115
115
  methods, or a task class such as `RecaptchaV2TaskProxyless` to read the documentation.
116
116
  Signature help shows argument names, types, and defaults while you type a call.
117
117
 
118
+ The `captcha_solver_api/py.typed` file is intentionally empty. It is the standard
119
+ [PEP 561](https://peps.python.org/pep-0561/) marker that tells type checkers to use
120
+ the inline type annotations shipped with this package.
121
+
118
122
  Docstrings describe parameters, solution fields, exceptions, and usage examples.
119
123
  Task examples assume an existing synchronous `client`; with `AsyncCaptchaClient`,
120
124
  use `solution = await client.solve(task)` instead. Async snippets run inside an
@@ -191,15 +195,19 @@ on the target page.
191
195
  |---|---|---|
192
196
  | reCAPTCHA v2 | ✅ | ✅ |
193
197
  | reCAPTCHA v2 Enterprise | ✅ | ✅ |
194
- | reCAPTCHA v3 | ✅ | ❌ |
198
+ | reCAPTCHA v3 | ✅ | - |
195
199
  | Cloudflare Turnstile | ✅ | ✅ |
196
200
  | GeeTest v3 | ✅ | ✅ |
197
201
  | GeeTest v4 | ✅ | ✅ |
198
- | Image to Text | ✅ | ❌ |
202
+ | Image to Text | ✅ | - |
199
203
  | Yandex SmartCaptcha | ✅ | ✅ |
200
- | Coordinates (click captcha) | ✅ | ❌ |
204
+ | Yandex SmartCaptcha Image | ✅ | - |
205
+ | Coordinates (click captcha) | ✅ | - |
201
206
  | Tencent | ✅ | ✅ |
202
207
 
208
+ A dash (`-`) means that this CAPTCHA type has no separate task class that accepts
209
+ your own proxy; it does not indicate that solving the CAPTCHA is unsupported.
210
+
203
211
  ## Client Reference
204
212
 
205
213
  Every method below is available on both `CaptchaClient` (sync, `requests`-based) and
@@ -247,14 +255,17 @@ solution -- wraps `create_task()` + `get_task_result()` so you don't poll by han
247
255
 
248
256
  Returns the `solution` dict once `status` is `"ready"` -- its shape depends on
249
257
  the task type (see [Captcha Types](#captcha-types)).
250
- Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
258
+ Raises `ValidationError` before any request if `task` is not a task object or a
259
+ required task parameter is missing or empty. Otherwise raises `ApiError`,
260
+ `CaptchaTimeoutError`, or `NetworkError`.
251
261
 
252
262
  ### `create_task(task, language_pool=None)`
253
263
 
254
264
  Submits `task` and returns its numeric task ID without waiting for a solution.
255
265
  Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
256
266
  manage polling yourself (e.g. checking on many tasks from a different process).
257
- Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
267
+ Raises `ValidationError` before any request if `task` is invalid (same rules as
268
+ `solve()`). Otherwise raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
258
269
 
259
270
  ### `get_task_result(task_id)`
260
271
 
@@ -519,7 +530,7 @@ print(result["validate"])
519
530
  print(result["seccode"])
520
531
  ```
521
532
  `challenge` is session-specific and expires quickly, so it can't be hardcoded
522
- 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)
533
+ 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)
523
534
  for where the fetch belongs in the flow.
524
535
 
525
536
  ```python
@@ -539,11 +550,9 @@ With proxy, use `GeeTestTask` (same proxy fields as reCAPTCHA v2).
539
550
  <sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha)</sup>
540
551
 
541
552
  Use this method to solve the token-based Yandex SmartCaptcha and obtain a token
542
- for the widget on the target page. Use the coordinates method for image challenges.
553
+ for the widget on the target page. Use the dedicated image method for image challenges.
543
554
 
544
- `YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based
545
- challenge. For the image challenge instead, use `CoordinatesTask` with
546
- [Coordinates](#coordinates-click-captcha) is available for generic click captchas.
555
+ `YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based challenge.
547
556
 
548
557
  | Parameter | Required | Description |
549
558
  |---|---|---|
@@ -572,15 +581,45 @@ print(result["token"])
572
581
  With proxy, use `YandexSmartCaptchaTask` (same proxy fields as reCAPTCHA v2,
573
582
  plus the `https` option above).
574
583
 
584
+ #### Image challenge
585
+
586
+ Use `YandexSmartCaptchaImageTask` when SmartCaptcha shows a main image and a
587
+ separate strip indicating the symbols to click. The SDK sends API v2 type
588
+ `SmartCaptchaTask`; both images must be base64-encoded.
589
+
590
+ | Parameter | Required | Description |
591
+ |---|---|---|
592
+ | `image` | yes | Main captcha image, base64-encoded. |
593
+ | `imgInstructions` | yes | Click-order instruction image, base64-encoded. |
594
+ | `comment` | no | Optional text hint for the worker. |
595
+
596
+ **Response:** `coordinates` -- ordered `{"x": int, "y": int}` click positions.
597
+
598
+ ```python
599
+ import base64
600
+ from captcha_solver_api import CaptchaClient
601
+ from captcha_solver_api.tasks import YandexSmartCaptchaImageTask
602
+
603
+ with open("captcha.png", "rb") as f:
604
+ image = base64.b64encode(f.read()).decode("utf-8")
605
+ with open("instructions.png", "rb") as f:
606
+ instructions = base64.b64encode(f.read()).decode("utf-8")
607
+
608
+ task = YandexSmartCaptchaImageTask(
609
+ image=image,
610
+ imgInstructions=instructions,
611
+ )
612
+ result = CaptchaClient("your_api_key").solve(task)
613
+ print(result["coordinates"])
614
+ ```
615
+
575
616
  ### Coordinates (click captcha)
576
617
 
577
618
  <sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#coordinates)</sup>
578
619
 
579
- Use this method to identify points that a worker should click in an image. It
580
- supports generic click captchas and the image version of Yandex SmartCaptcha.
620
+ Use this method to identify points that a worker should click in a generic image.
581
621
 
582
- `CoordinatesTask`. Used both for generic "click on X" captchas and for Yandex
583
- SmartCaptcha's image challenge. No proxy variant -- the image is submitted directly.
622
+ `CoordinatesTask`. No proxy variant -- the image is submitted directly.
584
623
 
585
624
  | Parameter | Required | Description |
586
625
  |---|---|---|
@@ -674,6 +713,16 @@ task lifetime. A local timeout does not cancel a task or mean that the API
674
713
  failed to solve it. Use `create_task()` and keep its `task_id` if you need to
675
714
  check the same task later; calling `solve()` again creates a new task.
676
715
 
716
+ While polling, `solve()` retries short network failures and HTTP timeouts of
717
+ `getTaskResult` on the next poll, so a brief outage does not lose an already
718
+ created task. After `poll_retries` (default 3) consecutive failures the error is
719
+ raised. API errors are not retried, and `createTask` is never retried because
720
+ that could create and pay for a second task. To change the limit:
721
+
722
+ ```python
723
+ client.poll_retries = 5
724
+ ```
725
+
677
726
  ### Worker language pool
678
727
 
679
728
  Set a default `language_pool` once at construction instead of passing it to every call:
@@ -743,13 +792,26 @@ try:
743
792
  except ValidationError as e:
744
793
  print(f"Invalid argument: {e}")
745
794
  except ApiError as e:
746
- print(f"API error: {e.error_code} {e.error_description}")
747
- except CaptchaTimeoutError:
748
- print("Task timed out")
795
+ print(f"API error: {e.error_code} {e.error_description} (task {e.task_id})")
796
+ except CaptchaTimeoutError as e:
797
+ print(f"Task {e.task_id} timed out")
749
798
  except NetworkError as e:
750
799
  print(f"Network error: {e}")
751
800
  ```
752
801
 
802
+ Every SDK error has a `task_id` attribute. It is set when the error happens
803
+ after the task was created (while polling in `solve()` or in `get_task_result()`)
804
+ and is `None` otherwise. A polling timeout does not cancel the task, so you can
805
+ keep checking it:
806
+
807
+ ```python
808
+ try:
809
+ result = client.solve(task)
810
+ except CaptchaTimeoutError as e:
811
+ if e.task_id is not None:
812
+ status = client.get_task_result(e.task_id)
813
+ ```
814
+
753
815
  `TimeoutError` is still exported as a deprecated alias of `CaptchaTimeoutError`.
754
816
  Avoid importing it by name: it shadows Python's built-in `TimeoutError`.
755
817
 
@@ -758,16 +820,17 @@ Avoid importing it by name: it shadows Python's built-in `TimeoutError`.
758
820
  See the dedicated [examples documentation](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/README.md) for the full
759
821
  sync/async example list, setup steps, expected results, and placeholder guidance.
760
822
 
761
- - **Image/click captchas** (`image_to_text.py`, `coordinates.py`) run after installing the dependencies and setting a valid
823
+ - **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
824
+ `yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
762
825
  `CAPTCHA_API_KEY` -- they read sample images bundled in
763
826
  [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
764
827
  `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
765
- - **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
766
- `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
828
+ - **Token captchas** (`recaptcha_v2_*.py`, `recaptcha_v2_enterprise_*.py`, `recaptcha_v3.py`,
829
+ `turnstile_*.py`, `yandex_smartcaptcha_*.py`, `geetest_v4_*.py`, `tencent_*.py`) use
767
830
  placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
768
831
  `YOUR_CAPTCHA_ID`) -- replace these with the real values from your own target
769
832
  page before running.
770
- - **`geetest_v3.py`** additionally needs `challenge` fetched fresh for every
833
+ - **`geetest_v3_*.py`** additionally needs `challenge` fetched fresh for every
771
834
  request -- it's single-use and expires within seconds, so it can't be
772
835
  hardcoded into a static example. `"https://target-site.com/path/to/geetest/init"`
773
836
  is a placeholder; replace it with a request to your own target's equivalent
@@ -783,6 +846,7 @@ export CAPTCHA_API_KEY=your_api_key
783
846
  python examples/sync/balance.py
784
847
  python examples/sync/image_to_text.py
785
848
  python examples/sync/coordinates.py
849
+ python examples/sync/yandex_smartcaptcha_image.py
786
850
  ```
787
851
 
788
852
  Live checks with real parameters have exercised all supported task types.
@@ -807,8 +871,8 @@ Full API reference: https://captcha-solver.com/en/docs/captcha-types
807
871
  - [Selenium Python examples](https://github.com/captcha-solver-api/captcha-solver-selenium-python-examples)
808
872
  - [JavaScript examples](https://github.com/captcha-solver-api/javascript-examples)
809
873
  - [Tencent CAPTCHA automation examples](https://github.com/captcha-solver-api/How-to-Automate-Tencent-CAPTCHA)
810
- - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) — a working browser automation example for Cloudflare Turnstile.
811
- - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) — a step-by-step guide with Python and JavaScript SDK examples.
874
+ - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) - a working browser automation example for Cloudflare Turnstile.
875
+ - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) - a step-by-step guide with Python and JavaScript SDK examples.
812
876
 
813
877
  ## License
814
878
 
@@ -75,6 +75,10 @@ enabled in your editor, hover over `CaptchaClient`, `AsyncCaptchaClient`, their
75
75
  methods, or a task class such as `RecaptchaV2TaskProxyless` to read the documentation.
76
76
  Signature help shows argument names, types, and defaults while you type a call.
77
77
 
78
+ The `captcha_solver_api/py.typed` file is intentionally empty. It is the standard
79
+ [PEP 561](https://peps.python.org/pep-0561/) marker that tells type checkers to use
80
+ the inline type annotations shipped with this package.
81
+
78
82
  Docstrings describe parameters, solution fields, exceptions, and usage examples.
79
83
  Task examples assume an existing synchronous `client`; with `AsyncCaptchaClient`,
80
84
  use `solution = await client.solve(task)` instead. Async snippets run inside an
@@ -151,15 +155,19 @@ on the target page.
151
155
  |---|---|---|
152
156
  | reCAPTCHA v2 | ✅ | ✅ |
153
157
  | reCAPTCHA v2 Enterprise | ✅ | ✅ |
154
- | reCAPTCHA v3 | ✅ | ❌ |
158
+ | reCAPTCHA v3 | ✅ | - |
155
159
  | Cloudflare Turnstile | ✅ | ✅ |
156
160
  | GeeTest v3 | ✅ | ✅ |
157
161
  | GeeTest v4 | ✅ | ✅ |
158
- | Image to Text | ✅ | ❌ |
162
+ | Image to Text | ✅ | - |
159
163
  | Yandex SmartCaptcha | ✅ | ✅ |
160
- | Coordinates (click captcha) | ✅ | ❌ |
164
+ | Yandex SmartCaptcha Image | ✅ | - |
165
+ | Coordinates (click captcha) | ✅ | - |
161
166
  | Tencent | ✅ | ✅ |
162
167
 
168
+ A dash (`-`) means that this CAPTCHA type has no separate task class that accepts
169
+ your own proxy; it does not indicate that solving the CAPTCHA is unsupported.
170
+
163
171
  ## Client Reference
164
172
 
165
173
  Every method below is available on both `CaptchaClient` (sync, `requests`-based) and
@@ -207,14 +215,17 @@ solution -- wraps `create_task()` + `get_task_result()` so you don't poll by han
207
215
 
208
216
  Returns the `solution` dict once `status` is `"ready"` -- its shape depends on
209
217
  the task type (see [Captcha Types](#captcha-types)).
210
- Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
218
+ Raises `ValidationError` before any request if `task` is not a task object or a
219
+ required task parameter is missing or empty. Otherwise raises `ApiError`,
220
+ `CaptchaTimeoutError`, or `NetworkError`.
211
221
 
212
222
  ### `create_task(task, language_pool=None)`
213
223
 
214
224
  Submits `task` and returns its numeric task ID without waiting for a solution.
215
225
  Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
216
226
  manage polling yourself (e.g. checking on many tasks from a different process).
217
- Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
227
+ Raises `ValidationError` before any request if `task` is invalid (same rules as
228
+ `solve()`). Otherwise raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
218
229
 
219
230
  ### `get_task_result(task_id)`
220
231
 
@@ -479,7 +490,7 @@ print(result["validate"])
479
490
  print(result["seccode"])
480
491
  ```
481
492
  `challenge` is session-specific and expires quickly, so it can't be hardcoded
482
- 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)
493
+ 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)
483
494
  for where the fetch belongs in the flow.
484
495
 
485
496
  ```python
@@ -499,11 +510,9 @@ With proxy, use `GeeTestTask` (same proxy fields as reCAPTCHA v2).
499
510
  <sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha)</sup>
500
511
 
501
512
  Use this method to solve the token-based Yandex SmartCaptcha and obtain a token
502
- for the widget on the target page. Use the coordinates method for image challenges.
513
+ for the widget on the target page. Use the dedicated image method for image challenges.
503
514
 
504
- `YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based
505
- challenge. For the image challenge instead, use `CoordinatesTask` with
506
- [Coordinates](#coordinates-click-captcha) is available for generic click captchas.
515
+ `YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based challenge.
507
516
 
508
517
  | Parameter | Required | Description |
509
518
  |---|---|---|
@@ -532,15 +541,45 @@ print(result["token"])
532
541
  With proxy, use `YandexSmartCaptchaTask` (same proxy fields as reCAPTCHA v2,
533
542
  plus the `https` option above).
534
543
 
544
+ #### Image challenge
545
+
546
+ Use `YandexSmartCaptchaImageTask` when SmartCaptcha shows a main image and a
547
+ separate strip indicating the symbols to click. The SDK sends API v2 type
548
+ `SmartCaptchaTask`; both images must be base64-encoded.
549
+
550
+ | Parameter | Required | Description |
551
+ |---|---|---|
552
+ | `image` | yes | Main captcha image, base64-encoded. |
553
+ | `imgInstructions` | yes | Click-order instruction image, base64-encoded. |
554
+ | `comment` | no | Optional text hint for the worker. |
555
+
556
+ **Response:** `coordinates` -- ordered `{"x": int, "y": int}` click positions.
557
+
558
+ ```python
559
+ import base64
560
+ from captcha_solver_api import CaptchaClient
561
+ from captcha_solver_api.tasks import YandexSmartCaptchaImageTask
562
+
563
+ with open("captcha.png", "rb") as f:
564
+ image = base64.b64encode(f.read()).decode("utf-8")
565
+ with open("instructions.png", "rb") as f:
566
+ instructions = base64.b64encode(f.read()).decode("utf-8")
567
+
568
+ task = YandexSmartCaptchaImageTask(
569
+ image=image,
570
+ imgInstructions=instructions,
571
+ )
572
+ result = CaptchaClient("your_api_key").solve(task)
573
+ print(result["coordinates"])
574
+ ```
575
+
535
576
  ### Coordinates (click captcha)
536
577
 
537
578
  <sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#coordinates)</sup>
538
579
 
539
- Use this method to identify points that a worker should click in an image. It
540
- supports generic click captchas and the image version of Yandex SmartCaptcha.
580
+ Use this method to identify points that a worker should click in a generic image.
541
581
 
542
- `CoordinatesTask`. Used both for generic "click on X" captchas and for Yandex
543
- SmartCaptcha's image challenge. No proxy variant -- the image is submitted directly.
582
+ `CoordinatesTask`. No proxy variant -- the image is submitted directly.
544
583
 
545
584
  | Parameter | Required | Description |
546
585
  |---|---|---|
@@ -634,6 +673,16 @@ task lifetime. A local timeout does not cancel a task or mean that the API
634
673
  failed to solve it. Use `create_task()` and keep its `task_id` if you need to
635
674
  check the same task later; calling `solve()` again creates a new task.
636
675
 
676
+ While polling, `solve()` retries short network failures and HTTP timeouts of
677
+ `getTaskResult` on the next poll, so a brief outage does not lose an already
678
+ created task. After `poll_retries` (default 3) consecutive failures the error is
679
+ raised. API errors are not retried, and `createTask` is never retried because
680
+ that could create and pay for a second task. To change the limit:
681
+
682
+ ```python
683
+ client.poll_retries = 5
684
+ ```
685
+
637
686
  ### Worker language pool
638
687
 
639
688
  Set a default `language_pool` once at construction instead of passing it to every call:
@@ -703,13 +752,26 @@ try:
703
752
  except ValidationError as e:
704
753
  print(f"Invalid argument: {e}")
705
754
  except ApiError as e:
706
- print(f"API error: {e.error_code} {e.error_description}")
707
- except CaptchaTimeoutError:
708
- print("Task timed out")
755
+ print(f"API error: {e.error_code} {e.error_description} (task {e.task_id})")
756
+ except CaptchaTimeoutError as e:
757
+ print(f"Task {e.task_id} timed out")
709
758
  except NetworkError as e:
710
759
  print(f"Network error: {e}")
711
760
  ```
712
761
 
762
+ Every SDK error has a `task_id` attribute. It is set when the error happens
763
+ after the task was created (while polling in `solve()` or in `get_task_result()`)
764
+ and is `None` otherwise. A polling timeout does not cancel the task, so you can
765
+ keep checking it:
766
+
767
+ ```python
768
+ try:
769
+ result = client.solve(task)
770
+ except CaptchaTimeoutError as e:
771
+ if e.task_id is not None:
772
+ status = client.get_task_result(e.task_id)
773
+ ```
774
+
713
775
  `TimeoutError` is still exported as a deprecated alias of `CaptchaTimeoutError`.
714
776
  Avoid importing it by name: it shadows Python's built-in `TimeoutError`.
715
777
 
@@ -718,16 +780,17 @@ Avoid importing it by name: it shadows Python's built-in `TimeoutError`.
718
780
  See the dedicated [examples documentation](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/README.md) for the full
719
781
  sync/async example list, setup steps, expected results, and placeholder guidance.
720
782
 
721
- - **Image/click captchas** (`image_to_text.py`, `coordinates.py`) run after installing the dependencies and setting a valid
783
+ - **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
784
+ `yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
722
785
  `CAPTCHA_API_KEY` -- they read sample images bundled in
723
786
  [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
724
787
  `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
725
- - **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
726
- `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
788
+ - **Token captchas** (`recaptcha_v2_*.py`, `recaptcha_v2_enterprise_*.py`, `recaptcha_v3.py`,
789
+ `turnstile_*.py`, `yandex_smartcaptcha_*.py`, `geetest_v4_*.py`, `tencent_*.py`) use
727
790
  placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
728
791
  `YOUR_CAPTCHA_ID`) -- replace these with the real values from your own target
729
792
  page before running.
730
- - **`geetest_v3.py`** additionally needs `challenge` fetched fresh for every
793
+ - **`geetest_v3_*.py`** additionally needs `challenge` fetched fresh for every
731
794
  request -- it's single-use and expires within seconds, so it can't be
732
795
  hardcoded into a static example. `"https://target-site.com/path/to/geetest/init"`
733
796
  is a placeholder; replace it with a request to your own target's equivalent
@@ -743,6 +806,7 @@ export CAPTCHA_API_KEY=your_api_key
743
806
  python examples/sync/balance.py
744
807
  python examples/sync/image_to_text.py
745
808
  python examples/sync/coordinates.py
809
+ python examples/sync/yandex_smartcaptcha_image.py
746
810
  ```
747
811
 
748
812
  Live checks with real parameters have exercised all supported task types.
@@ -767,8 +831,8 @@ Full API reference: https://captcha-solver.com/en/docs/captcha-types
767
831
  - [Selenium Python examples](https://github.com/captcha-solver-api/captcha-solver-selenium-python-examples)
768
832
  - [JavaScript examples](https://github.com/captcha-solver-api/javascript-examples)
769
833
  - [Tencent CAPTCHA automation examples](https://github.com/captcha-solver-api/How-to-Automate-Tencent-CAPTCHA)
770
- - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) — a working browser automation example for Cloudflare Turnstile.
771
- - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) — a step-by-step guide with Python and JavaScript SDK examples.
834
+ - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) - a working browser automation example for Cloudflare Turnstile.
835
+ - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) - a step-by-step guide with Python and JavaScript SDK examples.
772
836
 
773
837
  ## License
774
838
 
@@ -0,0 +1 @@
1
+ __version__ = "1.1.0"
@@ -15,10 +15,12 @@ import httpx
15
15
  from ._version import __version__
16
16
  from .exceptions import (
17
17
  ApiError,
18
+ CaptchaError,
18
19
  CaptchaTimeoutError,
19
20
  NetworkError,
20
21
  ValidationError,
21
22
  )
23
+ from .tasks import BaseTask
22
24
 
23
25
 
24
26
  class AsyncCaptchaClient:
@@ -42,7 +44,7 @@ class AsyncCaptchaClient:
42
44
  the account default. Can be overridden in `solve()` or `create_task()`.
43
45
 
44
46
  Raises:
45
- ValidationError: `client_key` is empty.
47
+ ValidationError: `client_key` is missing, empty, or whitespace.
46
48
 
47
49
  Example:
48
50
  from captcha_solver_api import AsyncCaptchaClient
@@ -57,6 +59,10 @@ class AsyncCaptchaClient:
57
59
  token = solution["gRecaptchaResponse"]
58
60
  """
59
61
 
62
+ #: Consecutive `getTaskResult` network failures or HTTP timeouts that
63
+ #: `solve()` tolerates before raising. Each retry happens on the next poll.
64
+ poll_retries: int = 3
65
+
60
66
  def __init__(
61
67
  self,
62
68
  client_key: str,
@@ -84,9 +90,9 @@ class AsyncCaptchaClient:
84
90
  pool.
85
91
 
86
92
  Raises:
87
- ValidationError: `client_key` is empty.
93
+ ValidationError: `client_key` is missing, empty, or whitespace.
88
94
  """
89
- if not client_key:
95
+ if not isinstance(client_key, str) or not client_key.strip():
90
96
  raise ValidationError("client_key is required")
91
97
 
92
98
  self.client_key = client_key
@@ -165,6 +171,8 @@ class AsyncCaptchaClient:
165
171
  The numeric task ID to pass to `get_task_result()`.
166
172
 
167
173
  Raises:
174
+ ValidationError: `task` is not a task object, or a required task
175
+ parameter is missing or empty. Raised before any request is sent.
168
176
  ApiError: The API rejected the task or its parameters.
169
177
  NetworkError: The request failed at the transport level.
170
178
  CaptchaTimeoutError: The HTTP request timed out.
@@ -172,6 +180,9 @@ class AsyncCaptchaClient:
172
180
  Example:
173
181
  task_id = await client.create_task(task, language_pool="en")
174
182
  """
183
+ if not isinstance(task, BaseTask):
184
+ raise ValidationError("task must be an instance of BaseTask")
185
+
175
186
  payload: Dict[str, Any] = {
176
187
  "clientKey": self.client_key,
177
188
  "task": task.to_dict(),
@@ -216,8 +227,12 @@ class AsyncCaptchaClient:
216
227
  "clientKey": self.client_key,
217
228
  "taskId": task_id,
218
229
  }
219
- data = await self._request("getTaskResult", payload)
220
- self._ensure_success(data)
230
+ try:
231
+ data = await self._request("getTaskResult", payload)
232
+ self._ensure_success(data)
233
+ except CaptchaError as exc:
234
+ exc.task_id = task_id
235
+ raise
221
236
  return data
222
237
 
223
238
  async def get_balance(self) -> float:
@@ -273,10 +288,17 @@ class AsyncCaptchaClient:
273
288
  or the README's method reference.
274
289
 
275
290
  Raises:
291
+ ValidationError: `task` is not a task object, or a required task
292
+ parameter is missing or empty. Raised before any request is sent.
276
293
  ApiError: The API rejected the task or reported a solving error.
277
294
  CaptchaTimeoutError: Polling exceeded its deadline or an HTTP request
278
295
  timed out.
279
296
  NetworkError: A request failed or the API returned an invalid response.
297
+ While polling, up to `poll_retries` consecutive network failures or
298
+ HTTP timeouts are retried on the next poll before this is raised.
299
+
300
+ Errors raised after the task is created have `task_id` set, so the
301
+ task can still be checked with `get_task_result(exc.task_id)`.
280
302
 
281
303
  Example:
282
304
  from captcha_solver_api.tasks import RecaptchaV2TaskProxyless
@@ -292,18 +314,35 @@ class AsyncCaptchaClient:
292
314
  task_id = await self.create_task(task, language_pool=language_pool)
293
315
 
294
316
  deadline = time.monotonic() + (timeout if timeout is not None else self.timeout)
317
+ poll_failures = 0
295
318
 
296
319
  while time.monotonic() < deadline:
297
320
  remaining = deadline - time.monotonic()
298
321
  await asyncio.sleep(min(self.polling_interval, max(0, remaining)))
299
322
  if time.monotonic() >= deadline:
300
323
  break
301
- result = await self.get_task_result(task_id)
324
+ try:
325
+ result = await self.get_task_result(task_id)
326
+ except (NetworkError, CaptchaTimeoutError):
327
+ # getTaskResult is safe to repeat: retry a short transport
328
+ # failure on the next poll instead of losing a paid task.
329
+ poll_failures += 1
330
+ if poll_failures > self.poll_retries:
331
+ raise
332
+ continue
333
+ poll_failures = 0
302
334
 
303
335
  if result.get("status") == "ready":
304
336
  solution = result["solution"]
305
337
  if not isinstance(solution, dict):
306
- raise NetworkError("API returned a ready task without a solution object")
338
+ raise NetworkError(
339
+ "API returned a ready task without a solution object",
340
+ task_id=task_id,
341
+ )
307
342
  return solution
308
343
 
309
- raise CaptchaTimeoutError("Task solving timed out.")
344
+ raise CaptchaTimeoutError(
345
+ f"Task {task_id} was not solved within the polling timeout. It may still "
346
+ f"be solved later: check it with get_task_result({task_id}).",
347
+ task_id=task_id,
348
+ )