Skip to content

Python SDK

Verified v1.0.0· 2026-05-28

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_tokentoken
Verified v1.0.0· 2026-05-28

reCAPTCHA v2 (token)Solve a reCAPTCHA v2 token challenge via browser automation (async — polls until ready).

signaturesolve_recaptcha_v2_token(…)returnssolution.token
from 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 field

Returns 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 a website_url / website_key, the SDK polls our infrastructure and returns a ready-to-submit result["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_tokentoken
Verified v1.0.0· 2026-05-28

reCAPTCHA v2 (token)Solve a reCAPTCHA v2 token challenge via browser automation (async — polls until ready).

signaturesolve_recaptcha_v2_token(…)returnssolution.token
result = 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's g-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's g-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_turnstiletoken
Verified v1.0.0· 2026-05-28

Cloudflare TurnstileSolve a Cloudflare Turnstile token challenge (async — polls until ready).

signaturesolve_turnstile(…)returnssolution.token
result = solver.solve_turnstile(
    website_url="https://example.com",
    website_key="0x4AAAAAAA...",
    proxy="http://user:pass@host:port",  # optional
)
token = result["token"]

Returns the Turnstile token — submit it as the form's cf-turnstile-response.

REST task typeAntiTurnstileTask

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.

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 returns result["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 by geetest_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 by type.

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 by type.


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
)
OptionDefaultDescription
transport"grpc"Wire protocol. "grpc" sends images with zero base64 overhead; "http" (requires the [http] extra) sends REST/JSON with base64-encoded images.
urlapi.captchasonic.com:443Override the API endpoint (e.g. for self-hosted or staging).
timeout30.0Per-call network timeout, in seconds.
polling_interval2.0Seconds between polls while waiting for a token task.
polling_timeout120.0Maximum seconds to wait for a token task before raising.
secureTrueUse 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}")
errors7 canonical `errorId` codes
idPython exceptionNode errorNameMeaningAction
0SonicErrorSonicErrorBase class for every SDK error — catches anything below.Inspect `error.errorId` (Node) / `err.error_id` (Python) and branch.
1InvalidApiKeyErrorInvalidApiKeyErrorAPI key is missing, malformed, or revoked.Verify the key in your dashboard.
2InsufficientBalanceErrorInsufficientBalanceErrorAccount balance can't cover the task.Add funds.
3DailyLimitExceededErrorDailyLimitExceededErrorDaily quota exhausted.Wait for the daily reset or upgrade your plan.
4MinuteLimitExceededErrorMinuteLimitExceededErrorPer-minute rate limit hit.Slow down requests / add exponential backoff.
5QuotaExceededErrorQuotaExceededErrorPlan quota exhausted.Upgrade your plan.
6PlanExpiredErrorPlanExpiredErrorSubscription 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_cloudflare fails immediately. The proxy argument 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.) return result["typed_solution"] describing the action to take; only *_token, solve_turnstile, and solve_cloudflare return result["token"].

Resources