Python SDK
Solve CAPTCHAs from Python with the official CaptchaSonic SDK — gRPC + HTTP transport, async-ready, fully typed.
The official CaptchaSonic Python SDK turns every supported CAPTCHA into a single function call. It ships with both a synchronous (CaptchaSonic) and an asynchronous (AsyncCaptchaSonic) client, talks to our infrastructure over gRPC by default (with an optional HTTP transport), and includes full type hints, automatic retries for transient errors, and built-in polling for token-style challenges.
Installation
pip install captchasonic
The gRPC transport is installed by default. To also use the optional HTTP/JSON transport, install the extra:
pip install "captchasonic[http]"
TIP
The SDK supports Python 3.10, 3.11, 3.12, and 3.13 and is published under the MIT license.
Authentication
You need a CaptchaSonic API key. Create an account and grab your key from the dashboard, then top up your balance from Add funds.
Pass the key as the first positional argument to the client:
from captchasonic import CaptchaSonic
solver = CaptchaSonic("YOUR_API_KEY")
WARNING
Treat your API key like a password. Keep it out of source control — load it from an environment variable or a secrets manager instead of hard-coding it.
import os
from captchasonic import CaptchaSonic
solver = CaptchaSonic(os.environ["CAPTCHASONIC_API_KEY"])
Quick Start
Most token-style CAPTCHAs (reCAPTCHA, Turnstile, hCaptcha) are solved with a single call. The client polls for you and returns the token once the task is ready.
solve_recaptcha_v2_tokentokenreCAPTCHA v2 (token) — Solve a reCAPTCHA v2 token challenge via browser automation (async — polls until ready).
solve_recaptcha_v2_token(…)returnssolution.tokenfrom captchasonic import CaptchaSonic
solver = CaptchaSonic("YOUR_API_KEY")
result = solver.solve_recaptcha_v2_token(
website_url="https://example.com/login",
website_key="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
)
print(result["token"])
# 03AGdBq25SxXT... ← drop this into the g-recaptcha-response fieldimport { CaptchaSonic } from "captchasonic";
const solver = new CaptchaSonic(process.env.CAPTCHASONIC_API_KEY);
const result = await solver.solveRecaptchaV2Token({
websiteURL: "https://example.com/login",
websiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
});
console.log(result.solution.gRecaptchaResponse);
// 03AGdBq25SxXT... ← drop into the g-recaptcha-response fieldReturns the reCAPTCHA token — submit it as the page's
g-recaptcha-response.
Token-style methods poll for up to 120 seconds by default before timing out (see Configuration).
Supported CAPTCHA Types
The SDK splits methods into two families:
- Token methods (
*_token,solve_turnstile,solve_cloudflare) — you supply awebsite_url/website_key, the SDK polls our infrastructure and returns a ready-to-submitresult["token"]. - Image / interactive methods — you supply the challenge images yourself, and the SDK returns a
result["typed_solution"]describing what to click, drag, or type.
All image arguments accept the flexible ImageInput type — a file path (str), a pathlib.Path, raw bytes, or an open binary file object.
from pathlib import Path
# Any of these work as an item in an `images=[...]` list:
Path("captcha.png") # pathlib.Path
"./captcha.png" # str path
open("captcha.png", "rb").read() # bytes
reCAPTCHA v2 (token)
solve_recaptcha_v2_tokentokenreCAPTCHA v2 (token) — Solve a reCAPTCHA v2 token challenge via browser automation (async — polls until ready).
solve_recaptcha_v2_token(…)returnssolution.tokenresult = solver.solve_recaptcha_v2_token(
website_url="https://example.com",
website_key="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
proxy="http://user:pass@host:port", # optional
)
token = result["token"]
Returns
result["token"]— submit it as the page'sg-recaptcha-response.
reCAPTCHA v3 (token)
result = solver.solve_recaptcha_v3_token(
website_url="https://example.com",
website_key="6Lc_aCMTAAAAAB...",
proxy="http://user:pass@host:port", # optional
)
token = result["token"]
Returns
result["token"]— submit it as the page'sg-recaptcha-response.
reCAPTCHA v2 (image grid)
When you already have the challenge tiles, solve the grid directly and receive the indices to click.
result = solver.solve_recaptcha_v2(
images=[Path("tile.png")], # ImageInput list
question="traffic lights", # label or /m/... entity id
question_type="44", # "split_33" | "33" | "44"
)
to_click = result["typed_solution"]["grid"]["objects"] # list[int]
Returns
result["typed_solution"]["grid"]["objects"]— the list of tile indices to click.
Cloudflare Turnstile
solve_turnstiletokenCloudflare Turnstile — Solve a Cloudflare Turnstile token challenge (async — polls until ready).
solve_turnstile(…)returnssolution.tokenresult = solver.solve_turnstile(
website_url="https://example.com",
website_key="0x4AAAAAAA...",
proxy="http://user:pass@host:port", # optional
)
token = result["token"]await solver.solveTurnstile({
websiteURL: "https://example.com",
websiteKey: "0x4AAAAAAA...",
proxy: "http://user:[email protected]:8080",
});REST task typeReturns the Turnstile token — submit it as the form's
cf-turnstile-response.
AntiTurnstileTask
Cloudflare Challenge
The full Cloudflare challenge requires a proxy.
result = solver.solve_cloudflare(
website_url="https://example.com",
website_key="0x4AAAAAAA...",
proxy="http://user:pass@host:port", # required
)
token = result["token"]
Returns
result["token"]— the clearance token for the protected request.
Popular CAPTCHA (hCaptcha-style image challenges)
solve_popular_captcha handles the interactive image variants; solve_popular_captcha_token returns a token directly.
# Image / interactive variant
result = solver.solve_popular_captcha(
images=[Path("challenge.png")],
question="Select all traffic lights",
question_type="grid", # "objectClassify" | "objectClick" | "objectDrag" | "grid"
)
to_click = result["typed_solution"]["grid"]["objects"] # list[int]
# Token variant
result = solver.solve_popular_captcha_token(
website_url="https://example.com",
website_key="00000000-0000-0000-0000-000000000000",
proxy="http://user:pass@host:port", # optional
)
token = result["token"]
The image variant returns
result["typed_solution"]["grid"]["objects"](tile indices to click); the token variant returnsresult["token"].
GeeTest
result = solver.solve_geetest(
geetest_type="nine", # "nine" | "click" | "slide" | "match" | "winlinze"
question="Select all bicycles", # required for "nine" and "click"
images=[Path("tile.png")], # required for "nine", "click", "slide"
)
solution = result["typed_solution"] # structure varies by geetest_type
Returns
result["typed_solution"]— the action to perform, shaped bygeetest_type.
AWS WAF
result = solver.solve_aws_waf(
images=[Path("grid.png")],
question="grid:vehicles:cars",
)
to_click = result["typed_solution"]["grid"]["objects"] # list[int]
Returns
result["typed_solution"]["grid"]["objects"]— the list of tile indices to click.
Image-to-text (OCR)
result = solver.solve_ocr(
images=[Path("captcha.png")],
module="common", # "common" | "mtcaptcha" | "bls" | "morocco"
numeric=False, # expect only digits
case_sensitive=True, # preserve letter case
min_length=4,
max_length=8,
)
text = result["typed_solution"]["text"]["texts"][0] # str
Returns
result["typed_solution"]["text"]["texts"][0]— the recognized text to type in.
Slide puzzle
Pass the background and the puzzle piece; you get back the horizontal pixel offset to slide.
result = solver.solve_slide_image(
images=[Path("background.png"), Path("piece.png")],
)
offset_x = result["typed_solution"]["slide"]["x"] # float (pixels)
Returns
result["typed_solution"]["slide"]["x"]— the horizontal pixel offset to drag the piece.
TikTok
result = solver.solve_tiktok(
type="whirl", # "click" | "whirl" | "slide"
images=[Path("outer.png")],
examples=[Path("inner.png")], # required for "whirl" and "slide"
)
solution = result["typed_solution"]
Returns
result["typed_solution"]— the action to perform, shaped bytype.
Binance
result = solver.solve_binance(
type="grid", # "grid" | "slide"
question="Select all bicycles", # required for "grid"
images=[Path("grid.png")],
)
solution = result["typed_solution"]
Returns
result["typed_solution"]— the action to perform, shaped bytype.
Async Usage
For high-concurrency workloads (FastAPI handlers, Scrapy spiders, asyncio pipelines) use AsyncCaptchaSonic. It mirrors the synchronous API exactly, but every solve method is awaitable. Use it as an async context manager so the underlying channel is cleaned up automatically.
import asyncio
from captchasonic import AsyncCaptchaSonic
async def main():
async with AsyncCaptchaSonic("YOUR_API_KEY") as solver:
result = await solver.solve_turnstile(
website_url="https://example.com",
website_key="0x4AAAAAAA...",
)
print(result["token"])
asyncio.run(main())
The synchronous client supports the context-manager protocol too:
with CaptchaSonic("YOUR_API_KEY") as solver:
result = solver.solve_ocr(images=[Path("captcha.png")])
print(result["typed_solution"]["text"]["texts"][0])
Proxy Support
Token methods accept an optional proxy argument (required for solve_cloudflare). Use a standard proxy URL:
proxy = "http://user:pass@host:port"
result = solver.solve_recaptcha_v2_token(
website_url="https://example.com",
website_key="6Le-...",
proxy=proxy,
)
TIP
Supplying a proxy lets our solver mirror your request's geographic location and IP reputation, which improves success rates on sites with strict anti-bot rules.
Configuration
All options are passed to the client constructor and apply to both the sync and async clients.
solver = CaptchaSonic(
"YOUR_API_KEY",
transport="grpc", # "grpc" (default) or "http"
url="api.captchasonic.com:443", # endpoint override (optional)
timeout=30.0, # per-call timeout, seconds
polling_interval=2.0, # seconds between task polls
polling_timeout=120.0, # max wait for a token task, seconds
secure=True, # use TLS for gRPC
)
| Option | Default | Description |
|---|---|---|
transport | "grpc" | Wire protocol. "grpc" sends images with zero base64 overhead; "http" (requires the [http] extra) sends REST/JSON with base64-encoded images. |
url | api.captchasonic.com:443 | Override the API endpoint (e.g. for self-hosted or staging). |
timeout | 30.0 | Per-call network timeout, in seconds. |
polling_interval | 2.0 | Seconds between polls while waiting for a token task. |
polling_timeout | 120.0 | Maximum seconds to wait for a token task before raising. |
secure | True | Use TLS for the gRPC channel. |
Transient gRPC errors are retried automatically (up to 3 attempts) with exponential backoff, so you only need to handle real business errors.
Error Handling
All SDK errors inherit from SonicError, so you can catch everything with one except clause or branch on the specific subclass. Each error also carries a numeric error_id.
from captchasonic.exceptions import (
SonicError,
InvalidApiKeyError,
InsufficientBalanceError,
)
try:
result = solver.solve_turnstile(
website_url="https://example.com",
website_key="0x4AAAAAAA...",
)
except InsufficientBalanceError:
print("Top up your balance to continue.")
except InvalidApiKeyError:
print("Check your API key.")
except SonicError as err:
print(f"Solve failed (error_id={err.error_id}): {err}")
| id | Python exception | Node errorName | Meaning | Action |
|---|---|---|---|---|
0 | SonicError | SonicError | Base class for every SDK error — catches anything below. | Inspect `error.errorId` (Node) / `err.error_id` (Python) and branch. |
1 | InvalidApiKeyError | InvalidApiKeyError | API key is missing, malformed, or revoked. | Verify the key in your dashboard. |
2 | InsufficientBalanceError | InsufficientBalanceError | Account balance can't cover the task. | Add funds. |
3 | DailyLimitExceededError | DailyLimitExceededError | Daily quota exhausted. | Wait for the daily reset or upgrade your plan. |
4 | MinuteLimitExceededError | MinuteLimitExceededError | Per-minute rate limit hit. | Slow down requests / add exponential backoff. |
5 | QuotaExceededError | QuotaExceededError | Plan quota exhausted. | Upgrade your plan. |
6 | PlanExpiredError | PlanExpiredError | Subscription has expired. | Renew your subscription. |
Every concrete row inherits from SonicError — catch it once to handle all SDK failures, or branch on errorId / exception subclass for granular control. Transient gRPC errors are retried automatically with exponential backoff (up to 3 attempts).
Account Helpers
balance = solver.get_balance() # current balance in USD (float)
print(f"Balance: ${balance:.2f}")
health = solver.health_check() # HealthCheckResponse — verifies connectivity
Use health_check() as a lightweight readiness probe in services and get_balance() to gate jobs before you start spending credits.
Troubleshooting
TIP
ModuleNotFoundError when using HTTP transport. The HTTP client depends on httpx, which is an optional extra. Install it with pip install "captchasonic[http]" and set transport="http".
- A token task raises after ~120 seconds. That's the
polling_timeout. Increase it for slow targets, e.g.CaptchaSonic(key, polling_timeout=240.0). solve_cloudflarefails immediately. Theproxyargument is required for the Cloudflare challenge — supply a working proxy URL.- gRPC connection errors behind a corporate proxy/firewall. gRPC needs HTTP/2 over port 443. If your network blocks it, switch to
transport="http". - Image methods return indices, not a token. Interactive methods (
solve_recaptcha_v2,solve_popular_captcha,solve_aws_waf,solve_geetest, etc.) returnresult["typed_solution"]describing the action to take; only*_token,solve_turnstile, andsolve_cloudflarereturnresult["token"].