# Friendly Captcha: Captcha.fun API

> Solve Friendly Captcha proof-of-work widgets and submit the solution with the protected form.

## At a glance

- **`kind`**: `friendly_captcha` (Captcha.fun's IPs), or `friendly_captcha_proxy` with a `proxy` (your own IP)
- **You send**: `url`, `siteKey` (optional: `friendlyVersion`, `friendlyCdnOrigin`)
- **You get**: frc-captcha-solution value → `token`
- **Default price**: $1.00 per 1,000 successful solves (10 credits per solve). Your API key's rate may differ.
- **Flow**: `POST /v1/request` → `GET /v2/response/{id}` until `ready` (details below)

## Read this first

- Returns `frc-captcha-solution` for v1 widgets and `frc-captcha-response` for v2. Set `friendlyVersion` to the site’s widget line; add `-eu` for EU-region keys.
- Pass `friendlyCdnOrigin` if the site serves the widget from its own origin instead of the public CDN.

## Request: `POST /v1/request`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | string | yes | `friendly_captcha` · `friendly_captcha_proxy` |
| `url` | string | yes | Page URL where the challenge appears. |
| `siteKey` | string | yes | Site key of the challenge. |
| `friendlyVersion` | string | no | Widget line: `v1` (default), `v2`, `v1-eu`, or `v2-eu`. Must match the site |
| `friendlyCdnOrigin` | string | no | Widget script origin for self-hosted or EU sites. Omit for the public CDN |
| `proxy` | string | no | For `_proxy` kinds only: your proxy as `host:port` or `host:port:username:password`. Ignored by non-proxy kinds. If a `_proxy` kind is sent with no proxy, Captcha.fun's own IPs are used. |
| `platform` | string | no | Requested platform label: `windows` (default) or `mac`. |

Headers: `API_KEY: <key>`, `Content-Type: application/json`; optional `APP_ID` to tag requests.

Example body:

```json
{
  "kind": "friendly_captcha",
  "url": "https://your-domain.example/login",
  "siteKey": "YOUR_FRIENDLY_CAPTCHA_SITE_KEY"
}
```

## End-to-end example (Python)

Install `requests` (`python -m pip install requests`) and set `CAPTCHAFUN_API_KEY` in your environment. Replace the example URL, keys, and challenge inputs with your own values.

`poll_budget` limits polling and waits between requests. Requests' `timeout` limits connection/read inactivity, not total elapsed time; a slow streaming response can exceed the polling budget.

```python
import os, time, requests
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

API = "https://api.captcha.fun"
HEADERS = {"API_KEY": os.environ["CAPTCHAFUN_API_KEY"]}  # key looks like CAPFUN-XXXXXXXX...; never hardcode it


def solve(body, poll_budget=120):
    """Create once, then check the time budget between result reads and waits."""
    deadline = time.monotonic() + poll_budget
    solve_id = None

    def remaining():
        seconds = deadline - time.monotonic()
        if seconds <= 0:
            raise TimeoutError(f"Polling budget exhausted (solve ID: {solve_id}); an accepted solve may still be running")
        return seconds

    def wait(seconds):
        time.sleep(min(seconds, remaining()))

    def retry_after(response, fallback):
        value = response.headers.get("Retry-After", "").strip()
        try:
            return max(1, int(value))
        except ValueError:
            pass
        try:
            when = parsedate_to_datetime(value)
            if when.tzinfo is None:
                when = when.replace(tzinfo=timezone.utc)
            return max(1, (when - datetime.now(timezone.utc)).total_seconds())
        except (TypeError, ValueError, OverflowError):
            return fallback

    # A 429 rejects creation. Other ambiguous failures
    # are raised to the caller; never automatically repeat a POST after a timeout.
    for attempt in range(5):
        r = requests.post(f"{API}/v1/request", json=body, headers=HEADERS,
                          timeout=min(30, remaining()))
        if r.status_code != 429 or attempt == 4:
            break
        wait(retry_after(r, min(30, 2 ** attempt)))
    r.raise_for_status()
    solve_id = r.json()["id"]

    read_delay = 1
    while True:
        request_timeout = min(30, remaining())
        try:
            r = requests.get(f"{API}/v2/response/{solve_id}", headers=HEADERS,
                             timeout=request_timeout)
        except (requests.Timeout, requests.ConnectionError):
            wait(read_delay)
            read_delay = min(30, read_delay * 2)
            continue  # Retry only this result read; never create another solve.
        remaining()
        if r.status_code == 429 or 500 <= r.status_code < 600:
            wait(retry_after(r, read_delay))
            read_delay = min(30, read_delay * 2)
            continue  # Reads are safe to retry for the same solve ID.
        r.raise_for_status()
        res = r.json()
        if res["ready"]:
            return solve_id, res
        read_delay = 1
        wait(1)  # HTTP 202 + ready:false: the solve is still running.


body = {
    "kind": "friendly_captcha",
    "url": "https://your-domain.example/login",
    "siteKey": "YOUR_FRIENDLY_CAPTCHA_SITE_KEY",
}

solve_id, res = solve(body)

if not res["success"]:
    raise RuntimeError(res.get("message", "solve failed"))  # not billed; create a new solve to retry

token = res["token"]  # frc-captcha-solution value
```

## Result

A finished solve returns HTTP 200 with `ready: true`:

```json
{
  "ready": true,
  "status": "SUCCESS",
  "success": true,
  "userAgent": "RETURNED_USER_AGENT",
  "secUa": "RETURNED_SEC_CH_UA",
  "token": "e8e1a897de2ea1fa5db0d2fd8.ABCdEF.1735689600"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `token` | string | Solved value: `frc-captcha-solution` (v1) or `frc-captcha-response` (v2) |

Every v2 result carries `ready`, `status`, and `success`. Successful solves also provide `userAgent` and `secUa` for result usage; these may be empty while pending or on failure. Failed solves can include a `message`.

### Using the result

Submit the value under the field name the site expects (`frc-captcha-solution` for v1, `frc-captcha-response` for v2), then verify it server-side with your Friendly Captcha API key.

Solutions are single-use and expire roughly five minutes after minting — submit them immediately.

When using a successful result, preserve the returned `userAgent` as `User-Agent` and `secUa` as `Sec-CH-UA`.

If the target rejects a `SUCCESS` result, report it with `POST /v1/report/{id}`, using the saved `solve_id`:

```python
report = requests.post(f"{API}/v1/report/{solve_id}", headers=HEADERS, timeout=30)
report.raise_for_status()
```

Reports are accepted for 10 minutes after the solve finished; after that the report endpoint returns HTTP 404 `Request Not Found`.

## Protocol recap (same for every product)

- Base URL `https://api.captcha.fun`; auth header `API_KEY`; read the key from `CAPTCHAFUN_API_KEY`.
- `POST /v1/request` → `{"id": "…", "message": "Started Solving"}`. One call is one solve; do not re-send it to check progress.
- Poll `GET /v2/response/{id}` about once a second: HTTP 202 + `ready: false` while solving, HTTP 200 + `ready: true` when done. Do not use `GET /v1/response/{id}` unless you want a 60 s long-poll that reports `FAILURE` on timeout even if the solve is still running.
- A failed solve is HTTP 200 with `status: "FAILURE"` and a `message`; it is not billed. Only successful solves are charged.
- Always send the returned `userAgent` with the result. Cookie and header results are also bound to the solving session's egress IP: use the `_proxy` kind with a sticky `proxy` if your requests will come from elsewhere. `platform` is `windows` (default) or `mac`.
- HTTP errors are `{"message": "…"}`: 400 = fix the request (`API_KEY Header Missing` if no key is sent; `API_KEY Incorrect` if the solve belongs to another key), 429 = wait `Retry-After`. Retry 500 errors on result reads with backoff; do not automatically repeat a creation request whose outcome is unknown.

Full protocol: [overview](https://www.captcha.fun/docs/md/overview.md). Machine-readable: [OpenAPI JSON](https://www.captcha.fun/docs/openapi.json) (schema `FriendlyCaptchaSolveRequest`). [API reference](https://www.captcha.fun/docs/api/friendly-captcha).
