> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfsky.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CAPTCHA solving

> Enable CAPTCHA solvers, choose manual or automatic mode, and check the result.

Surfsky exposes CAPTCHA commands through a page-level CDP session. Enable `anti_captcha` when starting the browser, choose a solver for the challenge, and verify the page state after solving.

<span id="setup" />

## Enable solving

Add this to the browser start body:

```json theme={null}
{
  "anti_captcha": {
    "enabled": true,
    "auto_captcha_types": ["turnstile"]
  }
}
```

Use `auto_captcha_types` to restrict automatic detection to the types your page needs. It does not restrict an explicit manual `Captcha.solve` request.

### Provider configuration

| Setting                                | Purpose                                                                                                                  |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `enabled`                              | Enables the CAPTCHA subsystem. Defaults to `false`.                                                                      |
| `auto_captcha_types`                   | Types permitted in automatic mode. Set an explicit list for predictable behavior.                                        |
| `gemini_api_key`                       | Key used by solvers that require Gemini, unless configured on your account.                                              |
| `disable_external_providers`           | Skip loading the external provider integration. Defaults to `false`.                                                     |
| `external_providers_initially_enabled` | Load the external integration but control whether it begins enabled. Defaults to `true`; ignored if loading is disabled. |

Provider availability depends on your account configuration. reCAPTCHA, GeeTest, and BLS use the external provider integration. Image-to-text requires a configured CapMonster key. hCaptcha, PerimeterX, DataDome audio, and FunCaptcha use Gemini.

Check [solver balance](/api-reference/captcha/get-solver-balance) for the providers exposed by your account. A balance response does not verify that every provider key is valid or funded.

<span id="choose-the-right-mode" />

<span id="supported-captchas" />

<span id="two-solving-modes" />

## Choose a method

| Challenge                | Manual `Captcha.solve` | Automatic `Captcha.autoSolve` | Guide                                        |
| ------------------------ | ---------------------- | ----------------------------- | -------------------------------------------- |
| reCAPTCHA                | No                     | Yes, external provider        | [reCAPTCHA](/use-cases/recaptcha)            |
| GeeTest                  | No                     | Yes, external provider        | [GeeTest](/use-cases/geetest)                |
| BLS                      | No                     | Yes, external provider        | [BLS](/use-cases/bls)                        |
| Turnstile                | Yes                    | Yes                           | [Turnstile](/use-cases/turnstile)            |
| hCaptcha                 | Yes                    | Yes                           | Use `type: "hcaptcha"` with the setup below. |
| PerimeterX               | Yes                    | Yes                           | [PerimeterX](/use-cases/perimeterx)          |
| DataDome audio or slider | Yes                    | Yes                           | [DataDome](/use-cases/datadome)              |
| FunCaptcha audio         | Yes                    | Yes                           | [FunCaptcha](/use-cases/funcaptcha)          |
| A known click target     | Yes                    | No                            | [Click](/use-cases/click)                    |
| Image-to-text            | Yes                    | No                            | [Image](/use-cases/image)                    |

A supported type is not a guarantee that every version or site configuration will solve. Use a small test against the actual target and verify the protected content.

<span id="code-examples" />

<span id="quick-start" />

## Connect and solve

This Python example starts a browser, connects Playwright, and attempts a manual Turnstile solve. Install the packages and set your [Surfsky credentials](/quickstart#credentials):

```bash theme={null}
pip install requests playwright
export TARGET_URL="https://your-site.example/protected-page"
```

Replace `TARGET_URL` with the page you are testing. Save as `solve.py`:

```python theme={null}
import os
import requests
from playwright.sync_api import sync_playwright

base_url = os.environ["SURFSKY_API_BASE_URL"].rstrip("/")
headers = {"X-Cloud-Api-Token": os.environ["SURFSKY_API_TOKEN"]}
response = requests.post(
    f"{base_url}/profiles/one_time",
    headers=headers,
    json={
        "anti_captcha": {
            "enabled": True,
            "disable_external_providers": True,
            "auto_captcha_types": ["turnstile"],
        },
        "browser_settings": {"inactive_kill_timeout": 120},
    },
    timeout=120,
)
response.raise_for_status()
session = response.json()
try:
    with sync_playwright() as pw:
        browser = pw.chromium.connect_over_cdp(session["ws_url"])
        try:
            context = browser.contexts[0]
            page = context.pages[0] if context.pages else context.new_page()
            cdp = page.context.new_cdp_session(page)
            page.goto(os.environ["TARGET_URL"], wait_until="domcontentloaded")
            result = cdp.send("Captcha.solve", {
                "type": "turnstile",
                "timeout": 60000,
            })
            print(result)
            if result["status"] != "success":
                raise RuntimeError(f"Solve ended with {result['status']}")
            # Add a wait for your site's protected content here.
        finally:
            browser.close()
finally:
    stopped = requests.post(
        f"{base_url}/profiles/{session['internal_uuid']}/stop",
        headers=headers, timeout=120,
    )
    if stopped.status_code != 404:
        stopped.raise_for_status()
```

```bash theme={null}
python solve.py
```

A successful attempt returns `{"status": "success", "type": "turnstile"}`. If no challenge is present, manual detection can return a CDP error instead. The final API request stops the browser when the example exits.

For JavaScript, start with the [Playwright lifecycle](/quickstart/playwright), add `anti_captcha` to its start body, and create the CDP session with `await page.context().newCDPSession(page)`. The command parameters are the same.

<span id="api-reference" />

<span id="captcha-solve" />

<span id="captchasolve" />

<span id="cdp-commands" />

<span id="manual-mode" />

## Manual solving

`Captcha.solve` waits for one attempt to finish:

```javascript theme={null}
const result = await cdp.send("Captcha.solve", {
  type: "turnstile",
  timeout: 60000,
});
```

| Parameter                  | Meaning                                                                          |
| -------------------------- | -------------------------------------------------------------------------------- |
| `type`                     | Solver type. When omitted, Surfsky tries manual detection.                       |
| `timeout`                  | Overall timeout in milliseconds for the manual attempt.                          |
| `options.selector`         | Optional CSS selector, or `[x, y]` coordinates for click solving.                |
| `options.image_url`        | Required image URL for image-to-text.                                            |
| `options.timeout`          | Solver-specific timeout in milliseconds, where supported.                        |
| `options.wait_networkidle` | Solver-specific wait configuration; not an overall command timeout.              |
| `options.proxy`            | Solver-specific proxy URL, where supported; does not change the browser's proxy. |

Only pass options used by the selected solver. A field being accepted does not mean every solver uses it. See the type-specific guides for concrete examples.

Results include `status` and `type`. Statuses include `success`, `failed`, `timeout`, and `not_detected`; image solving also returns `solution` on success. Invalid parameters and detection failures may raise CDP errors instead of returning a status object.

<span id="auto-mode" />

<span id="auto-solving" />

<span id="captcha-autosolve" />

<span id="captchaautosolve" />

## Automatic solving

Use `Captcha.autoSolve` to start background detection on the page. Attach event listeners before starting it, then navigate:

```javascript theme={null}
cdp.on("Captcha.solveCompleted", (event) => {
  console.log("Solve completed:", event.type, event.status);
});
cdp.on("Captcha.solveFailed", (event) => {
  console.error("Solve failed:", event.type, event.status ?? event.error);
});

await cdp.send("Captcha.autoSolve", { type: "turnstile" });
await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
// Wait for a selector or response that identifies your protected content.
```

The response `{"status": "started"}` confirms that background solving started. It does not report a solved challenge. Use page conditions to decide when the job is complete, with a bounded timeout.

`type` restricts detection to one allowed type. Omit it to detect among the configured `auto_captcha_types`. Calling `autoSolve` again replaces the current background loop; disconnecting the CDP client ends it. Repeated failures can stop the loop, so observe failure events.

<span id="monitor-events" />

<span id="with-event-listeners" />

### Events

| Event                        | When it is emitted                               |
| ---------------------------- | ------------------------------------------------ |
| `Captcha.detectionStarted`   | Detection begins.                                |
| `Captcha.detectionCompleted` | Detection finishes.                              |
| `Captcha.solveStarted`       | A solve attempt begins.                          |
| `Captcha.solveCompleted`     | An attempt returns success.                      |
| `Captcha.solveFailed`        | An attempt fails, times out, or raises an error. |

Payload fields depend on the event. Solve events include the type when known, and may include `status`, `error`, or an image `solution`.

<span id="captcha-setexternalprovidersenabled" />

<span id="captchasetexternalprovidersenabled" />

## Control the external provider

If the browser loaded the external integration, toggle it on the current page with:

```javascript theme={null}
await cdp.send("Captcha.setExternalProvidersEnabled", { enabled: false });
await cdp.send("Captcha.setExternalProvidersEnabled", { enabled: true });
```

This does not disable internal solvers or stop the background detection loop. If the browser started with `disable_external_providers: true`, the integration is absent and this command returns an error. Start another browser with loading enabled if you need it.

<span id="best-practices" />

<span id="common-issues" />

<span id="handle-timeouts-properly" />

<span id="need-help" />

<span id="related" />

<span id="too-many-captchas" />

## Troubleshooting

| Symptom                                          | Check                                                                              |
| ------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Solver is not enabled                            | Set `anti_captcha.enabled: true` in the browser start request.                     |
| Type cannot be solved manually                   | Use automatic mode for reCAPTCHA, GeeTest, or BLS.                                 |
| Type is not allowed in automatic mode            | Include it in `auto_captcha_types` before starting the browser.                    |
| No CAPTCHA detected                              | Inspect the page and frame; wait for the challenge to appear.                      |
| `status: "started"` but content is still blocked | Observe solve events and wait for the actual page result.                          |
| Repeated failures                                | Check provider configuration, balance, proxy behavior, and current challenge type. |
| Solver succeeds but data is missing              | The page may still need navigation, form submission, or another wait.              |

Use [DevTools](/debugging) or [Screencast](/screencast) to observe one attempt before increasing concurrency. Keep provider keys out of logs and source control.
