Core SDK
The engine of the whole ecosystem. capsolver-core uses pure Python to detect CAPTCHAs on a page, read their parameters, solve them through the CapSolver AI service, and fill the token back in. The higher-level capsolver-agent and capsolver-mcp packages are both built on top of it.
Token mode only. This SDK solves by requesting a token from the CapSolver API (reCAPTCHA v2/v3, Cloudflare Turnstile). It does not click image grids or drag sliders on the page.
1. Overview
capsolver-core breaks “solving a CAPTCHA” into four clear stages, chained into a single pipeline:
- Detect (
detect) — identify which CAPTCHA types are present on the page. - Read parameters (
get_captcha_info) — read the structured parameters of each CAPTCHA (type, site key, URL, and so on) straight from the live page. - Solve (
solve) — hand the parameters to CapSolver’s AI service and get back a usable token. - Fill back (
solve_on_page) — write the token back into the page DOM so the site treats verification as passed.
Around this pipeline, it adds several engineering conveniences:
- ✅ Fully async API — built on
async/await, so you can run a large number of solves concurrently. - ✅ Connection reuse and cleanup — maintains an internal HTTP connection pool and works as an async context manager.
- ✅ Pluggable handler registry — each CAPTCHA type is handled by one handler, which you can customize or override.
- ✅ Two levels of granularity — go pure API when the parameters are known; use Playwright Browser mode when all you have is the page.
When should you use core directly? When you control the code yourself and don’t need to expose solving to an LLM.
2. Installation
capsolver-core is open source on GitHub and not yet published to PyPI, so install it via git:
pip install git+https://github.com/capsolver-ai/capsolver-core.git
# With Playwright support (for detect / solve_on_page)
pip install "capsolver-core[playwright] @ git+https://github.com/capsolver-ai/capsolver-core.git"
playwright install chromiumSet your key:
export CAPSOLVER_API_KEY="your-capsolver-api-key"Note: The SDK works without the
[playwright]extra — in that case only Token mode (solve) is available, and the browser-dependent methodsdetect/get_captcha_info/solve_on_pageare unavailable because Playwright is missing.
3. The API & the Two Modes
3.1 Create a Client
create_capsolver(**options) (or Capsolver(**options)) returns a client.
| Option | Default | Description |
|---|---|---|
api_key | — | CapSolver client key (required for solving) |
service | https://api.capsolver.com | API base URL |
default_timeout | 120 | Total polling budget, in seconds |
polling_interval | 5 | Interval between result polls, in seconds |
request_timeout_ms | 30000 | Timeout for a single HTTP request |
app_id | — | Developer / affiliate ID |
handlers | All built-in | Override the registered CAPTCHA handlers |
source | — | Traffic-source identifier |
version | — | Client version tag |
on_error | — | Callback for non-fatal errors |
Capsolver holds an internal HTTP connection pool, so it’s best used as an async context manager to ensure connections are released:
async with create_capsolver(api_key="YOUR_API_KEY") as cap:
solution = await cap.solve(info)
# or call await cap.aclose() explicitly when you're done3.2 Methods at a Glance
| Method | Parameters | Returns | Description |
|---|---|---|---|
solve(info, wait_options?) | CaptchaInfo, WaitOptions? | Solution | Solve from parameters |
detect(page) | Playwright page | list[CaptchaType] | Which CAPTCHAs are on the page |
get_captcha_info(page) | Playwright page | list[CaptchaInfo] | Structured parameters for each widget |
solve_on_page(page, options?) | page, SolveOnPageOptions? | list[SolveOnPageResult] | Detect → solve → fill back |
get_balance() | — | BalanceResp | Account balance |
register(handler) | CaptchaHandler | — | Add a handler to the registry |
get_supported_captchas() | — | list[str] | List the supported types |
get_handler(key) | str / CaptchaType | CaptchaHandler | None | Look up a handler by key |
3.3 Solve Input: CaptchaInfo
The core input to solve() is a CaptchaInfo. type, website_url, and website_key are required (an empty website_url or website_key raises ValueError); the rest are optional depending on the CAPTCHA type:
| Parameter | Type | Required | Applies to | Description |
|---|---|---|---|---|
type | CaptchaType | Yes | All | RECAPTCHA_V2 / RECAPTCHA_V3 / CLOUDFLARE |
website_url | str | Yes | All | Full URL of the page hosting the CAPTCHA |
website_key | str | Yes | All | Site public key (reCAPTCHA’s data-sitekey / Turnstile’s sitekey) |
version | str | No | reCAPTCHA | "v2" or "v3"; usually auto-detected |
page_action | str | No | reCAPTCHA v3 | The v3 action name (e.g. "login") |
min_score | float | No | reCAPTCHA v3 | Desired minimum score (0.0–1.0) |
invisible | bool | No | reCAPTCHA v2 | Whether this is invisible v2 |
enterprise | bool | No | reCAPTCHA | Whether to use the Enterprise variant |
s | str | No | reCAPTCHA Enterprise | The Enterprise s token |
cdata | str | No | Cloudflare | Turnstile’s cdata custom parameter |
proxy | str | No | All | Proxy, as user:pass@host:port or host:port |
user_agent | str | No | All | Custom User-Agent |
container_id | str | No | Browser fill-back | DOM id used to locate the widget container during fill-back |
callback | str | No | Browser fill-back | Name of the callback function to trigger after solving |
binded_button_id | str | No | Browser fill-back | DOM id of the associated submit button |
extra | dict | No | All | Pass-through for fields not yet modeled |
The options for solve_on_page() are controlled by SolveOnPageOptions:
| Option | Type | Default | Description |
|---|---|---|---|
autofill | bool | True | Whether to automatically fill the token back into the page DOM after solving |
throw_on_error | bool | False | On a single CAPTCHA failure, whether to raise an exception or record it in the error field |
timeout | float | Client default | Total polling budget for this call, in seconds |
polling_interval | float | Client default | Polling interval for this call, in seconds |
The wait_options for solve() (WaitOptions) has only two optional fields, timeout and polling_interval, used to override the client’s global defaults for a single solve.
3.4 Return Types
| Type | Field | Description |
|---|---|---|
Solution | token | The solved token — the credential you submit to the target site |
captcha_type | The CAPTCHA type that was solved | |
expire_time | Token expiry in seconds (only present when the service returns it) | |
user_agent | The UA used to solve (only present when the service returns it) | |
raw | The raw TokenSolution (contains g_recaptcha_response, etc.) | |
SolveOnPageResult | info | The CaptchaInfo for that CAPTCHA |
solution | The Solution; None on failure | |
filled | Whether the token was filled back into the page | |
error | Reason for failure; None on success | |
BalanceResp | balance | Account balance (float) |
packages | List of plans / subscription packages |
detect() returns list[CaptchaType], and get_captcha_info() returns list[CaptchaInfo].
3.5 How Each Mode Uses These APIs
The only difference between the two modes is where the parameters come from — which is why they call different APIs:
Token mode (you already know the site key) — uses only solve(). You construct the CaptchaInfo yourself and get a token back:
info = CaptchaInfo(type=CaptchaType.RECAPTCHA_V2, website_url=..., website_key=...)
solution = await cap.solve(info)Browser mode (driving the page with Playwright) — uses detect() / get_captcha_info() / solve_on_page(). The all-in-one solve_on_page() covers detection, solving, and fill-back; or you can run the steps separately to control each stage yourself:
results = await cap.solve_on_page(page) # all in one call
# or step by step:
types = await cap.detect(page)
infos = await cap.get_captcha_info(page)
solution = await cap.solve(infos[0])4. Calling Each API: Requests & Responses
This section walks through each method individually — the real call, the parameters to pass, a sample response, and an explanation of the response fields.
4.1 solve — Solve (Token mode)
Example call and response:
import asyncio
from capsolver_core import create_capsolver, CaptchaType, CaptchaInfo
async def main():
cap = create_capsolver(api_key="YOUR_API_KEY")
info = CaptchaInfo(
type=CaptchaType.RECAPTCHA_V2, # required: CAPTCHA type
website_url="https://example.com", # required: page URL
website_key="6Lc...", # required: site public key (data-sitekey)
# v3 also takes page_action / min_score; Enterprise takes enterprise/s; Cloudflare takes cdata
)
solution = await cap.solve(info)
print(solution.token)
asyncio.run(main())Sample response (Solution):
Solution(
token="03AFcWeA6f...", # the credential to submit to the target site
captcha_type=CaptchaType.RECAPTCHA_V2,
expire_time=120,
user_agent="Mozilla/5.0 ...",
raw=TokenSolution(...),
)Response fields:
token— the solved token; submit it asg-recaptcha-responsefor reCAPTCHA orcf-turnstile-responsefor Turnstile.captcha_type— the CAPTCHA type that was solved.expire_time— token expiry in seconds; present only when the service returns it, otherwiseNone.user_agent— the UA used to solve; present only when the service returns it, otherwiseNone.raw— the rawTokenSolution, containingg_recaptcha_response/token/user_agent/expire_time.
4.2 solve_on_page — Whole-page detect + solve + fill-back (Browser mode)
Call and parameters: the input is a Playwright page, with an optional SolveOnPageOptions.
import asyncio
from capsolver_core import create_capsolver
from playwright.async_api import async_playwright
async def main():
cap = create_capsolver(api_key="YOUR_API_KEY")
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto("https://example.com/login")
results = await cap.solve_on_page(page) # or pass options=SolveOnPageOptions(...)
for r in results:
print(r.info.type, r.solution.token if r.solution else None, r.filled, r.error)
asyncio.run(main())Sample response (list[SolveOnPageResult]):
[
SolveOnPageResult(
info=CaptchaInfo(type=CaptchaType.RECAPTCHA_V2, website_url=..., website_key=...),
solution=Solution(token="03AF...", captcha_type=CaptchaType.RECAPTCHA_V2),
filled=True,
error=None,
),
]Response fields (per item):
info— theCaptchaInfofor that CAPTCHA (type and parameters).solution— theSolution;Noneon failure.filled— whether the token was successfully filled back into the page.error— failure reason string;Noneon success.
4.3 detect — Detect which CAPTCHAs are on the page
Call and parameters: the input is a Playwright page.
types = await cap.detect(page)Sample response / fields (list[CaptchaType]):
[<CaptchaType.RECAPTCHA_V2: 'reCaptchaV2'>]Returns a list of the CAPTCHA-type enums detected on the page; an empty list means none were found.
4.4 get_captcha_info — Read structured parameters
Call and parameters: the input is a Playwright page.
infos = await cap.get_captcha_info(page)
for info in infos:
print(info.type, info.website_url, info.website_key)
solution = await cap.solve(infos[0]) # once read, can be solved directlySample response / fields (list[CaptchaInfo]):
# [CaptchaInfo(type=CaptchaType.RECAPTCHA_V2,
# website_url="https://example.com",
# website_key="6Lc...")]4.5 get_balance — Check balance
Call and parameters: no input.
balance = await cap.get_balance()
print(balance.balance, balance.packages)Sample response / fields (BalanceResp):
BalanceResp(balance=12.34, packages=[])balance— account balance (float).packages— list of plans / subscription packages; an empty list if there are none.
4.6 get_supported_captchas / get_handler — Registry access
Call and response:
cap.get_supported_captchas() # → ['reCaptchaV2', 'reCaptchaV3', 'cloudflare']get_supported_captchas()— returns a list of the currently supported type strings.