# Browser Use

> Add a custom action to Browser Use that solves a reCAPTCHA image challenge in the current tab with the Solvecha API, or load the Solvecha extension.

[Browser Use](https://github.com/browser-use/browser-use) lets an LLM drive a browser. There are two ways to add Solvecha.

## Option A: the extension

Start a Chrome that has the [Solvecha extension](https://solvecha.com/docs/browser-extension.md) and a debugging port, save your key in the extension, and give Browser Use the address:

```python
from browser_use import Agent, Browser, ChatBrowserUse

browser = Browser(cdp_url="http://127.0.0.1:9222")
agent = Agent(task="Open the page and log in", llm=ChatBrowserUse(), browser=browser)
await agent.run()
```

The extension solves the challenge by itself. See [Hermes Agent](https://solvecha.com/docs/hermes-agent.md) for the Chrome command.

## Option B: a custom action

This action takes a screenshot of the challenge area and sends it to the API. It returns the tiles to click. The agent then clicks them with its normal click action. It uses the REST API with `httpx`, so the key check and billing are the same as everywhere.

```python
import asyncio
import base64
import os

import httpx
from browser_use import ActionResult, Agent, BrowserSession, ChatBrowserUse, Tools

API = "https://api.solvecha.com"
KEY = os.environ["SOLVECHA_API_KEY"]

tools = Tools()


@tools.action(
    description=(
        "Solve a reCAPTCHA v2 image challenge that is visible in the current tab. "
        "Pass the instruction text (for example 'Select all images with buses') and the grid "
        "('3x3' or '4x4'). Returns the tile numbers to click, counted from 1, left to right, top to bottom."
    )
)
async def solve_recaptcha(task: str, grid: str, browser_session: BrowserSession) -> ActionResult:
    # Screenshot of the page. Crop to the challenge if you know its position.
    png = await browser_session.take_screenshot()
    image = base64.b64encode(png).decode() if isinstance(png, bytes) else png

    headers = {"Authorization": f"Bearer {KEY}"}
    async with httpx.AsyncClient(base_url=API, headers=headers, timeout=30) as client:
        job = (await client.post(
            "/v1/recognition/recaptcha",
            json={"task": task, "grid": grid, "image_data": [image]},
        )).json()
        if "data" not in job:
            return ActionResult(error=f"Solvecha error {job.get('error')}: {job.get('message')}")
        for _ in range(40):
            await asyncio.sleep(1)
            answer = (await client.get("/v1/recognition/recaptcha", params={"id": job["data"]})).json()
            if answer.get("error") == 14:  # still working
                continue
            if "data" in answer:
                tiles = [i + 1 for i, picked in enumerate(answer["data"]) if picked]
                return ActionResult(extracted_content=f"Click tiles {tiles}, then press Verify.")
            return ActionResult(error=f"Solvecha error {answer.get('error')}: {answer.get('message')}")
    return ActionResult(error="No answer in 40 seconds")


agent = Agent(task="...", llm=ChatBrowserUse(), tools=tools)
```


> **Note:**
> A screenshot of the whole page makes the grid small. For the best accuracy send a picture of only the 3x3 or 4x4 grid. Read the grid position from the page (for example the iframe of the challenge) and crop the screenshot before you send it. Or use the [MCP server](https://solvecha.com/docs/mcp-server.md) or the extension.


Restrict the action to the sites you automate with `allowed_domains` in the Browser Use action settings if you want to be strict.
