# Set up CaptchaNo in a project (guide for AI coding agents)

CaptchaNo is a captcha solving API. You send the captcha's details, it returns the token (or cookie, or text) the page expects. This guide tells an AI coding agent how to wire it into a user's project and into the agent's own toolset. Follow the steps in order.

- Website: https://captchano.com
- API reference: https://captchano.com/captcha-solver-api
- MCP server: https://captchano.com/agents/mcp (npm package `captchano-mcp`)
- Tools for testing: https://captchano.com/tools

## Ground rules

- **Authorised use only.** Solve captchas only on sites the user owns, has permission to automate, or whose terms allow it. If the target looks like ban evasion, account farming, scalping or fraud, stop and ask the user.
- **Never expose the API key.** Do not hardcode, print, log, commit or paste it into chat output. Read it from the environment (`CAPTCHANO_API_KEY`).
- **Bound spend.** Every successful solve is billed (failed solves are free). Use `CAPTCHANO_MAX_SPEND_USD` for the MCP server and a per-run cap in code.
- **Change as little as possible.** Reuse the project's language, HTTP client, config loading and code style.

## Step 1: Get the API key

Ask the user for a key. Keys start with `cn-`. They are created at https://captchano.com/dashboard/keys (new accounts get free trial credit; no card needed).

Store it where the project keeps secrets:

- a git-ignored `.env` / `.env.local` file: `CAPTCHANO_API_KEY=cn-...` (check `.gitignore` covers it before writing), or
- the deployment's secret store (Cloudflare `wrangler secret put`, Vercel/Netlify env vars, GitHub Actions secrets, etc.).

If the project has a `.env.example`, add `CAPTCHANO_API_KEY=` (empty) there.

## Step 2: Give yourself the MCP tool (if your client supports MCP)

The server runs with `npx -y captchano-mcp` (Node.js 20.3+). Configuration is by environment variable only.

**Claude Code**

```bash
claude mcp add captchano -e CAPTCHANO_API_KEY=cn-... -e CAPTCHANO_MAX_SPEND_USD=1.00 -- npx -y captchano-mcp
```

**Cursor** (`.cursor/mcp.json`), **Claude Desktop** (`claude_desktop_config.json`), **Windsurf, Cline, Zed**:

```json
{
  "mcpServers": {
    "captchano": {
      "command": "npx",
      "args": ["-y", "captchano-mcp"],
      "env": { "CAPTCHANO_API_KEY": "cn-...", "CAPTCHANO_MAX_SPEND_USD": "1.00" }
    }
  }
}
```

**VS Code (Copilot agent mode)** (`.vscode/mcp.json`) prompts for the key instead of storing it:

```json
{
  "inputs": [{ "type": "promptString", "id": "captchano-api-key", "description": "CaptchaNo API key (cn-...)", "password": true }],
  "servers": {
    "captchano": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "captchano-mcp"],
      "env": { "CAPTCHANO_API_KEY": "${input:captchano-api-key}", "CAPTCHANO_MAX_SPEND_USD": "1.00" }
    }
  }
}
```

Do not commit a config file that contains the real key; use the client's user-level config or an input prompt.

Tools the server provides: `solve_captcha`, `get_task` (resume a solve that is still running), `get_balance`, `list_captcha_types`.

## Step 3: Add a solve helper to the project's code (if it automates a browser or HTTP)

### The API

```
POST https://api.captchano.com/v1/solve?wait=60
Authorization: Bearer $CAPTCHANO_API_KEY
Content-Type: application/json
```

Body fields by type:

| type | required | optional |
|---|---|---|
| `recaptcha_v2` | url, sitekey | invisible, proxy, userAgent |
| `recaptcha_v3` | url, sitekey | action, minScore (0.1–0.9), proxy, userAgent |
| `recaptcha_enterprise` | url, sitekey | invisible, proxy, userAgent (checkbox-style Enterprise keys only) |
| `turnstile` | url, sitekey | action, cData, pageData, proxy, userAgent |
| `datadome` | url, challengeUrl, proxy | userAgent |
| `image` | body (base64 image) | – |

`proxy` is a string like `http://user:pass@host:port` (http, socks4, socks5).

Responses:

- `200` with `status: "ready"` and `solution` → `solution.token` (reCAPTCHA, Turnstile), `solution.cookie` (DataDome) or `solution.text` (image), plus `solution.userAgent`, `cost`, `solveTime`.
- `202` with `status: "solving"` and `id` → poll `GET https://api.captchano.com/v1/task/{id}` every 5 seconds until `ready` or `failed`.
- `4xx/5xx` with `error.code`: `invalid_api_key`, `insufficient_balance` (ask the user to top up at https://captchano.com/dashboard/topup), `unsupported_type`, `invalid_sitekey`, `proxy_required`, `proxy_unreachable`, `score_not_met`, `unsolvable`, `capacity_unavailable` and `rate_limited` (retry with backoff), `task_not_found`.

`GET https://api.captchano.com/v1/balance` returns `{ "balance": "12.3400", "currency": "USD" }`.

### Finding the inputs on a page

- **reCAPTCHA sitekey:** `data-sitekey` on `.g-recaptcha`, the `k=` parameter of the `recaptcha/api2/anchor` iframe, or `render=` on `recaptcha/api.js` (that one means v3). `recaptcha/enterprise.js` means Enterprise.
- **v3 action:** the `action` passed to `grecaptcha.execute(sitekey, { action })` in the page's JavaScript.
- **Turnstile sitekey:** `data-sitekey` on `.cf-turnstile`, or the `sitekey` passed to `turnstile.render`. Keys start with `0x4`.
- **DataDome:** a `captcha-delivery.com` iframe URL is the `challengeUrl`; you must pass the same proxy the browser uses.
- Quick check without a browser: https://captchano.com/tools/captcha-detector reports the type, sitekey and action for a URL.

### Helper (TypeScript / Node 18+)

```ts
export async function solveCaptcha(req: Record<string, unknown>): Promise<{ token?: string; cookie?: string; text?: string; userAgent?: string }> {
  const key = process.env.CAPTCHANO_API_KEY;
  if (!key) throw new Error("CAPTCHANO_API_KEY is not set");
  const headers = { Authorization: `Bearer ${key}`, "Content-Type": "application/json" };
  let res = await fetch("https://api.captchano.com/v1/solve?wait=60", { method: "POST", headers, body: JSON.stringify(req) });
  let task = await res.json();
  while (res.status === 202 || task.status === "solving" || task.status === "queued") {
    await new Promise((r) => setTimeout(r, 5000));
    res = await fetch(`https://api.captchano.com/v1/task/${task.id}`, { headers });
    task = await res.json();
  }
  if (task.status !== "ready") throw new Error(`captcha not solved: ${task.error?.code ?? res.status}`);
  return task.solution;
}
```

### Helper (Python 3)

```python
import os, time, requests

def solve_captcha(payload: dict) -> dict:
    headers = {"Authorization": f"Bearer {os.environ['CAPTCHANO_API_KEY']}"}
    r = requests.post("https://api.captchano.com/v1/solve", params={"wait": 60}, json=payload, headers=headers, timeout=130)
    task = r.json()
    while r.status_code == 202 or task.get("status") in ("queued", "solving"):
        time.sleep(5)
        r = requests.get(f"https://api.captchano.com/v1/task/{task['id']}", headers=headers, timeout=30)
        task = r.json()
    if task.get("status") != "ready":
        raise RuntimeError(f"captcha not solved: {task.get('error', {}).get('code', r.status_code)}")
    return task["solution"]
```

### Using the token in a browser (Playwright / Puppeteer)

```ts
// reCAPTCHA v2 / Enterprise: fill the hidden field, then fire the site's callback if it has one.
await page.evaluate((token) => {
  document.querySelectorAll('textarea[name="g-recaptcha-response"]').forEach((t) => ((t as HTMLTextAreaElement).value = token));
  const cb = document.querySelector("[data-sitekey]")?.getAttribute("data-callback");
  if (cb && typeof (window as any)[cb] === "function") (window as any)[cb](token);
}, solution.token);

// Turnstile: same idea with the cf-turnstile-response input.
await page.evaluate((token) => {
  document.querySelectorAll('input[name="cf-turnstile-response"]').forEach((i) => ((i as HTMLInputElement).value = token));
}, solution.token);
```

Selenium: run the same JavaScript with `driver.execute_script`. Scrapy / plain HTTP: send the token in the form field the site posts (`g-recaptcha-response` or `cf-turnstile-response`).

Tokens are valid for about two minutes and can be used once: request them right before submitting, and get a new one for every retry. If the site checks the IP or browser (Turnstile, DataDome), pass `proxy` and `userAgent` matching the client that will submit, and use the returned `solution.userAgent`.

Integration guides: https://captchano.com/for/playwright, https://captchano.com/for/puppeteer, https://captchano.com/for/selenium, https://captchano.com/for/scrapy, https://captchano.com/agents/langchain, https://captchano.com/agents/browser-use.

## Step 4: Verify

1. Balance: call the `get_balance` MCP tool, or `curl -H "Authorization: Bearer $CAPTCHANO_API_KEY" https://api.captchano.com/v1/balance`.
2. One end-to-end solve against the CaptchaNo demo, which uses real keys and checks the token with Google:
   - solve `{ "type": "recaptcha_v2", "url": "https://captchano.com/tools/captcha-demo", "sitekey": "6Lcfo_4tAAAAAIEt2ZGclFD5BQjSgo1XKWty1_zk" }`
   - then verify the token: `POST https://captchano.com/api/tools/verify` with JSON `{ "provider": "recaptcha", "token": "<token>" }`
   - expect `response.success: true`. Sending the same token again must fail with `timeout-or-duplicate` (tokens are single-use).
3. Report to the user: what you added (files, env var names, MCP config location), the spend cap you set, and how to run it. Do not show the key.

## What CaptchaNo does not solve

hCaptcha, GeeTest, Arkose Labs (FunCaptcha), AWS WAF and score-based reCAPTCHA Enterprise keys are not supported. If a target uses one, tell the user instead of guessing.
