# Captcha.fun API > One API key, one balance, every captcha and anti-bot solver. Use this page for the shared contract; each product has its own linked brief with exact fields and result handling. The machine-readable version is [OpenAPI JSON](https://www.captcha.fun/docs/openapi.json). Documentation describes inputs and outputs; product availability depends on the catalog and your API key. ## Quick start Every product works the same way: 1. `POST /v1/request` with the product `kind` and the page details. You get a solve `id` immediately. 2. `GET /v2/response/{id}` about once a second until `ready` is `true`. 3. Use the result (a token to submit, cookies to replay, or headers to replay), always with the returned `userAgent`. Install `requests` (`python -m pip install requests`) and set `CAPTCHAFUN_API_KEY` in your environment. Replace the example URL and site key. `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. The returned `solve_id` is retained for reporting a rejected result. ```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. solve_id, res = solve({ "kind": "turnstile", "url": "https://your-domain.example/login", "siteKey": "YOUR_TURNSTILE_SITE_KEY", }) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) print(res["token"]) ``` Same thing with curl: ```bash curl -X POST 'https://api.captcha.fun/v1/request' -H "API_KEY: $CAPTCHAFUN_API_KEY" -H 'Content-Type: application/json' \ -d '{"kind":"turnstile","url":"https://your-domain.example/login","siteKey":"YOUR_TURNSTILE_SITE_KEY"}' # {"id":"550e8400-e29b-41d4-a716-446655440000","message":"Started Solving"} curl 'https://api.captcha.fun/v2/response/550e8400-e29b-41d4-a716-446655440000' -H "API_KEY: $CAPTCHAFUN_API_KEY" # repeat until "ready": true ``` ## Pick a product The `kind` goes in the request body. Every product also has `_proxy`: it solves through your own `proxy` so the result comes from your IP. **You must send** lists the required body fields besides `kind`; if you do not have one, you cannot use that product yet. Price is USD per 1,000 successful solves at the default rate card. ### Captchas A widget on a form. You get a token (or, for GeeTest, a payload) to submit with the form or API call. | Product | `kind` | You must send | You get back | Price | Brief | | --- | --- | --- | --- | --- | --- | | reCAPTCHA v3 | `recap_v3`, `recap_v3_enterprise` | `url`, `siteKey`, `action` | reCAPTCHA v3 token (`token`) | `recap_v3` $1.00; `recap_v3_enterprise` $2.50 | [reCAPTCHA v3](https://www.captcha.fun/docs/md/recaptcha-v3.md) | | reCAPTCHA v2 | `recap_v2`, `recap_v2_invis`, `recap_v2_enterprise` | `url`, `siteKey` | g-recaptcha-response token (`token`) | `recap_v2` $1.50; `recap_v2_invis` $1.50; `recap_v2_enterprise` $2.50 | [reCAPTCHA v2](https://www.captcha.fun/docs/md/recaptcha-v2.md) | | Cloudflare Turnstile | `turnstile` | `url`, `siteKey` | cf-turnstile-response token (`token`) | $1.50 | [Cloudflare Turnstile](https://www.captcha.fun/docs/md/turnstile.md) | | hCaptcha Enterprise | `hcaptcha` | `url`, `siteKey` | h-captcha-response token (`token`) | $1.50 | [hCaptcha Enterprise](https://www.captcha.fun/docs/md/hcaptcha.md) | | hCaptcha Invisible | `hcaptcha_invis` | `url`, `siteKey` | h-captcha-response token (`token`) | $1.50 | [hCaptcha Invisible](https://www.captcha.fun/docs/md/hcaptcha-invisible.md) | | GeeTest | `geetest` | `url`, `siteKey` | GeeTest v4 or v3 validate values (`geetest`) | $1.50 | [GeeTest](https://www.captcha.fun/docs/md/geetest.md) | | Friendly Captcha | `friendly_captcha` | `url`, `siteKey` | frc-captcha-solution value (`token`) | $1.00 | [Friendly Captcha](https://www.captcha.fun/docs/md/friendly-captcha.md) | | CaptchaFox | `captchafox` | `url`, `siteKey` | CaptchaFox token (`token`) | $1.50 | [CaptchaFox](https://www.captcha.fun/docs/md/captchafox.md) | | FunCaptcha Invisible | `funcaptcha_invis` | `url`, `siteKey` | FunCaptcha-Token session token (`token`) | $1.50 | [FunCaptcha Invisible](https://www.captcha.fun/docs/md/funcaptcha-invisible.md) | ### Anti-bot A block page, challenge, or sensor protecting a site. You get cookies or headers to replay on requests to the protected origin (AWS WAF returns a token you send as a cookie). | Product | `kind` | You must send | You get back | Price | Brief | | --- | --- | --- | --- | --- | --- | | Cloudflare WAF | `cf_waf` | `url` | cf_clearance cookie (`cookie`) | $1.50 | [Cloudflare WAF](https://www.captcha.fun/docs/md/cloudflare-waf.md) | | DataDome | `datadome` | `url` | datadome cookie (`cookie`) | $2.00 | [DataDome](https://www.captcha.fun/docs/md/datadome.md) | | DataDome Interstitial | `datadome_interstitial` | `url` | datadome cookie (`cookie`) | $2.00 | [DataDome Interstitial](https://www.captcha.fun/docs/md/datadome-interstitial.md) | | PerimeterX | `perimeterx` | `url`, `challengeHtml` | _px* clearance cookie (`cookie`) | $2.00 | [PerimeterX](https://www.captcha.fun/docs/md/perimeterx.md) | | PerimeterX Hold | `perimeterx_hold` | `url`, `blockData` | _px3 and _pxhd cookies (`cookie`) | $3.00 | [PerimeterX Hold](https://www.captcha.fun/docs/md/perimeterx-hold.md) | | PerimeterX Invisible | `perimeterx_invisible` | `url` | Returned _px cookie family (such as _px3 or _px2); additional cookies vary by target (`cookie`) | $2.00 | [PerimeterX Invisible](https://www.captcha.fun/docs/md/perimeterx-invisible.md) | | Akamai (_abck) | `akamai_abck` | `url` | _abck session cookies (`cookie`) | $2.50 | [Akamai (_abck)](https://www.captcha.fun/docs/md/akamai-abck.md) | | Akamai (sbsd/bm_sc) | `akamai_sbsd` | `url` | sbsd and bm_sc cookies (`cookie`) | $2.50 | [Akamai (sbsd/bm_sc)](https://www.captcha.fun/docs/md/akamai-sbsd.md) | | Akamai (sec_cpt) | `akamai_sec_cpt` | `url` | sec_cpt cookies (`cookie`) | $2.50 | [Akamai (sec_cpt)](https://www.captcha.fun/docs/md/akamai-sec-cpt.md) | | Kasada | `kasada` | `url`, `protectedUrl` | x-kpsdk-* headers (`headers`) | $5.00 | [Kasada](https://www.captcha.fun/docs/md/kasada.md) | | Kasada (cd) | `kasada_cd` | `url`, `protectedUrl` | x-kpsdk-cd headers (`headers`) | $5.00 | [Kasada (cd)](https://www.captcha.fun/docs/md/kasada-cd.md) | | Kasada (tl payload) | `kasada_tl_payload` | `url` | A replayable /tl request (`headers`) | $5.00 | [Kasada (tl payload)](https://www.captcha.fun/docs/md/kasada-tl-payload.md) | | Imperva | `imperva` | `url` | Clearance cookies (reese84 plus session cookies) (`cookie`) | $2.00 | [Imperva](https://www.captcha.fun/docs/md/imperva.md) | | Incapsula (_utmvc) | `incapsula_utmvc` | `url` | _utmvc cookies (`cookie`) | $1.50 | [Incapsula (_utmvc)](https://www.captcha.fun/docs/md/incapsula-utmvc.md) | | Incapsula (reese84) | `reese84` | `url` | reese84 cookie (`cookie`) | $1.50 | [Incapsula (reese84)](https://www.captcha.fun/docs/md/reese84.md) | | AWS WAF | `aws_waf` | `url` | aws-waf-token cookie (`token`) | $2.00 | [AWS WAF](https://www.captcha.fun/docs/md/aws-waf.md) | | Shape | `shape` | `url`, `challengeHtml`, `protectedUrl` | Shape request headers (`headers`) | $5.00 | [Shape](https://www.captcha.fun/docs/md/shape.md) | | Vercel Challenge | `vercel_challenge` | `url` | Clearance cookies (`cookie`) | $1.50 | [Vercel Challenge](https://www.captcha.fun/docs/md/vercel-challenge.md) | ### Risk and session Device-risk and session products. You get a token, or cookies, to send with the protected call. | Product | `kind` | You must send | You get back | Price | Brief | | --- | --- | --- | --- | --- | --- | | Ticketmaster TMPT | `tmpt` | `url` | tmpt session token (`cookie`) | $2.50 | [Ticketmaster TMPT](https://www.captcha.fun/docs/md/tmpt.md) | | Castle | `castle` | `url` | One-time X-Castle-Request-Token (`token`) | $2.00 | [Castle](https://www.captcha.fun/docs/md/castle.md) | | Forter | `forter` | `url` | Forter token for the next request (`token`) | $2.00 | [Forter](https://www.captcha.fun/docs/md/forter.md) | | Riskified | `riskified` | `url` | Riskified cookies (`cookie`) | $2.00 | [Riskified](https://www.captcha.fun/docs/md/riskified.md) | | ThreatMetrix | `threatmetrix` | `url`, `threatMetrixOrgId`, `threatMetrixSessionId` | Your session ID, bound to a profile (`token`) | $1.50 | [ThreatMetrix](https://www.captcha.fun/docs/md/threatmetrix.md) | Prices come from the default rate card and may differ for your key. `GET /v1/products` returns `defaultCreditCost` per product (credits per solve ÷ 10 = USD per 1,000 solves). Result types: **token** = one value to submit; **cookie** = send `cookieHeader` as the `Cookie` header; **headers** = replay `headers` on one request to exactly the `protectedUrl` you gave; **geetest** / **text** = a payload or string to use directly. ## Protocol reference ### Authentication Send `API_KEY: ` on `/v1/request`, `/v1/response/{id}`, `/v2/response/{id}`, `/v1/report/{id}` and `/v1/stats`. Keys look like `CAPFUN-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`. Read the key from `CAPTCHAFUN_API_KEY`; never hardcode or log it. A missing or wrong key is HTTP 400, not 401. Optional `APP_ID` header tags a request for tracking. ### Endpoints | Method | Path | Purpose | | --- | --- | --- | | POST | `/v1/request` | Create a solve for any product. Body: JSON with `kind` plus the product's fields. Returns `{id, message}`. | | GET | `/v2/response/{id}` | Result. HTTP 202 + `ready:false` while solving; HTTP 200 + `ready:true` when finished. Use this one. | | GET | `/v1/response/{id}` | Long-poll: holds the connection up to 60 s, then returns `status:FAILURE` on timeout even if the solve is still running. | | POST | `/v1/report/{id}` | Report a `SUCCESS` result that the target rejected, using the saved solve id. Accepted for 10 minutes after the solve finished. Reporting does not change the result returned by subsequent reads. | | GET | `/v1/stats` | `{credits, success, failures}` for the key. | | GET | `/v1/products` | Products and `defaultCreditCost`. No auth. | ### Result envelope Both result endpoints return these fields for every product, plus the product-specific ones in its brief. | Field | Type | Description | | --- | --- | --- | | ready | boolean | v2 only. `true` once the solve has finished, successfully or not. | | status | string | `PENDING`, `SUCCESS`, or `FAILURE`. Reporting a rejected result does not change the result-read status. | | success | boolean | `true` only when `status` is `SUCCESS`. | | userAgent | string | On successful solves, send this value as `User-Agent`. Preserve it exactly; it can be empty while pending or on failure. | | secUa | string | On successful solves, send this value as `Sec-CH-UA`; it can be empty while pending or on failure. | | message | string | Failure reason; present only when the solve failed. | Product-specific fields: `token` (captcha tokens, AWS WAF, Castle, Forter, ThreatMetrix), `cookies` + `cookieHeader` (cookie products; `token` is the main cookie's bare value), `headers` (Shape, Kasada), `body` + `url` (Kasada tl payload), `geetest`. ```json // pending: HTTP 202 { "status": "PENDING", "success": false, "userAgent": "", "secUa": "", "token": "", "ready": false } // failed: HTTP 200 { "ready": true, "status": "FAILURE", "success": false, "userAgent": "", "secUa": "", "token": "", "message": "" } ``` ### Billing Only successful solves are charged. A solve that ends in `FAILURE` is not billed; create a new solve to retry. Check balance with `GET /v1/stats`. ### Proxy and platform - Non-proxy kinds use Captcha.fun's IPs. `_proxy` kinds solve through your `proxy` (`host:port` or `host:port:username:password`); if you send a `_proxy` kind without a proxy, Captcha.fun's IPs are used. - When replaying cookies or headers, preserve the returned `userAgent` and use the same egress IP as the solve. Use the `_proxy` kind with a **sticky** proxy when you need to control that IP. - `platform` (all products): `windows` (default) or `mac`. ### Replaying results - Send the returned `userAgent` (and `secUa`) with the result. Never substitute your own. - Tokens are single-use and short-lived: submit immediately. Header sets are single-use; Shape and Kasada sets are bound to the endpoint you passed as `protectedUrl`, so solve again for each endpoint. - Prefer the full `cookieHeader` over rebuilding a cookie jar from `cookies`. ### Errors | HTTP | When | | --- | --- | | 400 | Bad request. Messages: `API_KEY Header Missing`, `API_KEY Does Not Exist`, `API_KEY Has No Credits`, `API_KEY does not support this product`, `invalid kind`, ` solve requires `, `Invalid proxy format`; on result endpoints `API_KEY Incorrect` means the id belongs to another key. Fix the request; do not retry unchanged. | | 429 | Per-key request limit hit. Honor `Retry-After` (seconds or an HTTP-date), then retry. | | 500 | Server error. Retry result reads with backoff. If creation times out or returns a server error, its outcome can be unknown: do not automatically create another solve, which could be billed again. | [Docs](https://www.captcha.fun/docs) · [OpenAPI JSON](https://www.captcha.fun/docs/openapi.json) --- # reCAPTCHA v3: Captcha.fun API > Get standard and Enterprise reCAPTCHA v3 tokens. The site owner verifies each token with Google. ## At a glance - **`kind`**: `recap_v3` · `recap_v3_enterprise` (Captcha.fun's IPs), or `recap_v3_proxy` with a `proxy` (your own IP); the other `_proxy` kinds work the same way - **You send**: `url`, `siteKey`, `action` (optional: `isSession`) - **You get**: reCAPTCHA v3 token → `token` - **Default price**: `recap_v3`: $1.00 per 1,000 successful solves (10 credits per solve); `recap_v3_enterprise`: $2.50 per 1,000 successful solves (25 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 - Use `recap_v3` for standard reCAPTCHA v3 and `recap_v3_enterprise` for Google Cloud Enterprise site keys. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `recap_v3` · `recap_v3_enterprise` · `recap_v3_proxy` · `recap_v3_enterprise_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `action` | string | yes | The reCAPTCHA v3 `action` value the page passes to `grecaptcha.execute`. The API rejects the request (400) when it is empty. | | `isSession` | boolean | no | Appends `&waf=session` to the reCAPTCHA script URL (Google Cloud Armor session-token integration). | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "recap_v3_enterprise", "url": "https://your-domain.example/login", "siteKey": "YOUR_RECAPTCHA_SITE_KEY", "action": "login" } ``` ## 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": "recap_v3_enterprise", "url": "https://your-domain.example/login", "siteKey": "YOUR_RECAPTCHA_SITE_KEY", "action": "login", } 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"] # reCAPTCHA v3 token ``` ## 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": "03AGdBq25SiXT-pmSeBXjzScW-EiocHwwpwqJRCAI7Z..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved reCAPTCHA token | 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 `token` with the protected request. The site owner verifies the token, action, and hostname with Google. 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 `RecaptchaV3SolveRequest`). [API reference](https://www.captcha.fun/docs/api/recaptcha-v3). --- # reCAPTCHA v2: Captcha.fun API > Solve reCAPTCHA v2 checkbox, invisible, and Enterprise widgets and submit the token as `g-recaptcha-response`. ## At a glance - **`kind`**: `recap_v2` · `recap_v2_invis` · `recap_v2_enterprise` (Captcha.fun's IPs), or `recap_v2_proxy` with a `proxy` (your own IP); the other `_proxy` kinds work the same way - **You send**: `url`, `siteKey` - **You get**: g-recaptcha-response token → `token` - **Default price**: `recap_v2`: $1.50 per 1,000 successful solves (15 credits per solve); `recap_v2_invis`: $1.50 per 1,000 successful solves (15 credits per solve); `recap_v2_enterprise`: $2.50 per 1,000 successful solves (25 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 - Pick the kind that matches the site: `recap_v2` for the checkbox, `recap_v2_invis` for the invisible badge, or `recap_v2_enterprise` for Google Cloud Enterprise site keys. - **Important:** This product is marked coming soon on the public rate card. Check GET /v1/products and your API key's product access before sending a request. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `recap_v2` · `recap_v2_proxy` · `recap_v2_invis` · `recap_v2_invis_proxy` · `recap_v2_enterprise` · `recap_v2_enterprise_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "recap_v2", "url": "https://your-domain.example/login", "siteKey": "YOUR_RECAPTCHA_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": "recap_v2", "url": "https://your-domain.example/login", "siteKey": "YOUR_RECAPTCHA_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"] # g-recaptcha-response token ``` ## 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": "03AFcWeA5KFNfU0hVp7KJ8iEQJb2TOdRW8n2b..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved token. Submit it as `g-recaptcha-response` | 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 token as the `g-recaptcha-response` field of the protected form, then verify it with Google’s `siteverify` endpoint and your secret key. Tokens are single-use and expire after about two minutes. Submit right away, using the returned `userAgent`. 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 `RecaptchaV2SolveRequest`). [API reference](https://www.captcha.fun/docs/api/recaptcha-v2). --- # Cloudflare Turnstile: Captcha.fun API > Solve Cloudflare Turnstile widgets and submit the token as `cf-turnstile-response`. ## At a glance - **`kind`**: `turnstile` (Captcha.fun's IPs), or `turnstile_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` (optional: `action`, `turnstileMetadata`) - **You get**: cf-turnstile-response token → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Supports managed, non-interactive, and invisible widgets. Pass the widget's `action` and `cData` values when set. After `siteverify` succeeds, your server must compare the returned `action` and `cdata` with its expected values before accepting the request. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `turnstile` · `turnstile_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `action` | string | no | Widget `action`, if set. Your server must compare the `action` returned by `siteverify` with the expected value | | `turnstileMetadata` | object | no | Widget metadata, e.g. `{ "cData": "..." }`. Passed to the widget as-is | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "turnstile", "url": "https://your-domain.example/login", "siteKey": "YOUR_TURNSTILE_SITE_KEY", "action": "login" } ``` ## 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": "turnstile", "url": "https://your-domain.example/login", "siteKey": "YOUR_TURNSTILE_SITE_KEY", "action": "login", } 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"] # cf-turnstile-response token ``` ## 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": "0.ABCdEFGhIJKlMNoPQrSTUvWxYz0123456..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved token. Submit it as `cf-turnstile-response` | 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 token as `cf-turnstile-response` and verify it with Cloudflare's `siteverify` endpoint and your secret key. Require a successful response and compare the returned `action` and `cdata` with your expected values when set. Tokens are single-use and short-lived. Submit right away. 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 `TurnstileSolveRequest`). [API reference](https://www.captcha.fun/docs/api/turnstile). --- # hCaptcha Enterprise: Captcha.fun API > Solve hCaptcha Enterprise silent-pass challenges and submit the token as `h-captcha-response`. ## At a glance - **`kind`**: `hcaptcha` (Captcha.fun's IPs), or `hcaptcha_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` (optional: `hcaptchaRqdata`, `hcaptchaApiHost`) - **You get**: h-captcha-response token → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Supports silent-pass Enterprise sites; interactive image grids are not supported. If the page embeds `rqdata`, pass it as `hcaptchaRqdata`. The token is bound to it and `siteverify` checks it. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `hcaptcha` · `hcaptcha_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `hcaptchaRqdata` | string | no | Per-request `rqdata` blob the page embeds with the site key, if any | | `hcaptchaApiHost` | string | no | Custom hCaptcha API host for self-hosted or regional sites. Omit for `hcaptcha.com` | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "hcaptcha", "url": "https://your-domain.example/login", "siteKey": "YOUR_HCAPTCHA_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": "hcaptcha", "url": "https://your-domain.example/login", "siteKey": "YOUR_HCAPTCHA_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"] # h-captcha-response token ``` ## 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": "P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved token. Submit it as `h-captcha-response` | 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 token as the `h-captcha-response` field, then verify it with hCaptcha’s `siteverify` endpoint and your secret key. Tokens are single-use and short-lived. Submit right away, once, using the returned userAgent, from the same egress IP as the solve. 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 `HcaptchaSolveRequest`). [API reference](https://www.captcha.fun/docs/api/hcaptcha). --- # hCaptcha Invisible: Captcha.fun API > Solve invisible hCaptcha and submit the token as `h-captcha-response`. ## At a glance - **`kind`**: `hcaptcha_invis` (Captcha.fun's IPs), or `hcaptcha_invis_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` (optional: `hcaptchaRqdata`, `hcaptchaApiHost`) - **You get**: h-captcha-response token → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 - If the page embeds `rqdata`, pass it as `hcaptchaRqdata`. The token is bound to it and `siteverify` checks it. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `hcaptcha_invis` · `hcaptcha_invis_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `hcaptchaRqdata` | string | no | Per-request `rqdata` blob the page embeds with the site key, if any | | `hcaptchaApiHost` | string | no | Custom hCaptcha API host for self-hosted or regional sites. Omit for `hcaptcha.com` | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "hcaptcha_invis", "url": "https://your-domain.example/login", "siteKey": "YOUR_HCAPTCHA_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": "hcaptcha_invis", "url": "https://your-domain.example/login", "siteKey": "YOUR_HCAPTCHA_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"] # h-captcha-response token ``` ## 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": "P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved token. Submit it as `h-captcha-response` | 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 token as the `h-captcha-response` field, then verify it with hCaptcha’s `siteverify` endpoint and your secret key. Tokens are single-use and short-lived. Submit right away. 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 `HcaptchaInvisibleSolveRequest`). [API reference](https://www.captcha.fun/docs/api/hcaptcha-invisible). --- # GeeTest: Captcha.fun API > Solve GeeTest v4 and v3 challenges and submit the result for site-owner verification. ## At a glance - **`kind`**: `geetest` (Captcha.fun's IPs), or `geetest_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` (optional: `geetestChallenge`, `geetestApiServer`, `geetestProduct`, `geetestLang`, `geetestNewCaptcha`, `geetestRiskType`, `geetestUserInfo`) - **You get**: GeeTest v4 or v3 validate values → `geetest` - **Default price**: $1.50 per 1,000 successful solves (15 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 - v4 (default): `siteKey` is the `captchaId`. Returns four fields for GeeTest’s validate endpoint. - v3: `siteKey` is the `gt`, and `geetestChallenge` is the challenge from your own register call. Returns `geetest_challenge`, `geetest_validate`, and `geetest_seccode`. - Optional: `geetestApiServer` for self-hosted v3, `geetestRiskType` for gated v4, and `geetestUserInfo` when v4 is bound to the site’s session. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `geetest` · `geetest_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `geetestChallenge` | string | no | Required for v3. Challenge from your own register call; setting it switches the solve to v3. Challenges are single-use and short-lived — mint one fresh challenge per solve attempt and never retry a failed attempt with the same challenge | | `geetestApiServer` | string | no | v3 only. Self-hosted API host. Omit for GeeTest’s servers | | `geetestProduct` | string | no | Widget mode: `bind` (default), `popup`, or `float` | | `geetestLang` | string | no | Widget language. Defaults to `eng` / `en` | | `geetestNewCaptcha` | string | no | v3 only. `true` or `1` sends `new_captcha` on init | | `geetestRiskType` | string | no | Gated v4 sites only. Site-issued risk token; without it `/load` fails with `illegal risk_type` | | `geetestUserInfo` | string | no | v4 only. Site-issued `userInfo` JSON, e.g. `{"session_id":"..."}`, passed through as-is | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "geetest", "url": "https://your-domain.example/login", "siteKey": "YOUR_GEETEST_CAPTCHA_ID" } ``` ## 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": "geetest", "url": "https://your-domain.example/login", "siteKey": "YOUR_GEETEST_CAPTCHA_ID", } solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry payload = res["geetest"] # submit to the site's GeeTest validation endpoint ``` ## 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": "", "geetest": { "lot_number": "0241c2ef9dc0f0e5a3d0e1f5f2ad5c8d", "captcha_output": "5KAmMKVkldquKZ8DP5ehcG8LDv1brhRYgCbj9...", "pass_token": "8e035d781cd7f7c1d6c2f45ae2b0b8c8fb...", "gen_time": "1735689600" } } ``` | Field | Type | Description | | --- | --- | --- | | `geetest.lot_number` | string | v4 lot number | | `geetest.captcha_output` | string | v4 captcha output | | `geetest.pass_token` | string | v4 pass token | | `geetest.gen_time` | string | v4 generation time | | `geetest.geetest_challenge` | string | v3 challenge | | `geetest.geetest_validate` | string | v3 validate value | | `geetest.geetest_seccode` | string | v3 seccode | 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 v4: the site owner verifies `lot_number`, `captcha_output`, `pass_token`, and `gen_time` through GeeTest’s validate endpoint, using the HMAC-SHA256 `sign_token`. v3: submit `geetest_challenge`, `geetest_validate`, and `geetest_seccode` to the site’s validate endpoint. The result is single-use, so use it right away. 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 `GeetestSolveRequest`). [API reference](https://www.captcha.fun/docs/api/geetest). --- # 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: `, `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). --- # CaptchaFox: Captcha.fun API > Solve CaptchaFox challenges and submit the token with the protected form. ## At a glance - **`kind`**: `captchafox` (Captcha.fun's IPs), or `captchafox_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` - **You get**: CaptchaFox token → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Always include `url` and `siteKey`. Creation can accept a request without `url`, but that request cannot produce a usable solve result. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `captchafox` · `captchafox_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "captchafox", "url": "https://your-domain.example/login", "siteKey": "YOUR_CAPTCHAFOX_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": "captchafox", "url": "https://your-domain.example/login", "siteKey": "YOUR_CAPTCHAFOX_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"] # CaptchaFox token ``` ## 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": "cfx1.2.ABCdEFGhIJKlMNoPQrSTUvWxYz012..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Solved CaptchaFox token | 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 token with the protected form or API call, then verify it server-side with your CaptchaFox secret key. Tokens are single-use, short-lived, and scored against the solving egress — replay promptly, once, with the returned userAgent from the same proxy. 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 `CaptchafoxSolveRequest`). [API reference](https://www.captcha.fun/docs/api/captchafox). --- # FunCaptcha Invisible: Captcha.fun API > Solve invisible Arkose Labs FunCaptcha and replay the session token. ## At a glance - **`kind`**: `funcaptcha_invis` (Captcha.fun's IPs), or `funcaptcha_invis_proxy` with a `proxy` (your own IP) - **You send**: `url`, `siteKey` (optional: `funcaptchaBlob`, `funcaptchaSurl`) - **You get**: FunCaptcha-Token session token → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 the `FunCaptcha-Token` session value. If the site sends a `data[blob]`, pass it as `funcaptchaBlob`; the token is invalid without it. - Use `funcaptchaSurl` for sites on a private Arkose service URL. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `funcaptcha_invis` · `funcaptcha_invis_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `siteKey` | string | yes | Site key of the challenge. | | `funcaptchaBlob` | string | no | Arkose `data[blob]` enforcement blob, if the site uses one | | `funcaptchaSurl` | string | no | Arkose service URL for private deployments. Omit for `client-api.arkoselabs.com` | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "funcaptcha_invis", "url": "https://your-domain.example/login", "siteKey": "YOUR_ARKOSE_PUBLIC_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": "funcaptcha_invis", "url": "https://your-domain.example/login", "siteKey": "YOUR_ARKOSE_PUBLIC_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"] # FunCaptcha-Token session token ``` ## 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": "8542e3a1|r=ap-southeast-1|mt=..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Arkose session token: the `FunCaptcha-Token` value | 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 Send the token as the `FunCaptcha-Token` header or form field of the protected call. Arkose checks it against the public key and blob it was issued for. The token is single-use and bound to the solving session's fingerprint and egress — replay it promptly, once, using the returned userAgent, from the same proxy. 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 `FuncaptchaInvisibleSolveRequest`). [API reference](https://www.captcha.fun/docs/api/funcaptcha-invisible). --- # Cloudflare WAF: Captcha.fun API > Pass Cloudflare WAF managed challenges and replay the `cf_clearance` cookie. ## At a glance - **`kind`**: `cf_waf` (Captcha.fun's IPs), or `cf_waf_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `challengeHtml`) - **You get**: cf_clearance cookie → `token`, `cookies`, `cookieHeader` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Passes the Cloudflare managed challenge and returns the `cf_clearance` cookie together with the full Cookie header of the solve. - By default the challenge served for your target URL is used. If your integration captured the challenge page HTML, pass it in `challengeHtml`. - **Important:** The clearance is bound to the user agent and IP. If you replay from a different IP, use a `_proxy` kind with a sticky proxy. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `cf_waf` · `cf_waf_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `challengeHtml` | string | no | Captured challenge page HTML. Omit to use the challenge served for your target URL | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "cf_waf_proxy", "url": "https://your-domain.example/protected-page", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "cf_waf_proxy", "url": "https://your-domain.example/protected-page", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "v1e3b1f2e8e1a897de2ea1fa5db0d2fd8-1735689600-1.2.1...", "cookies": { "cf_clearance": "v1e3b1f2...", "__cf_bm": "a1b2c3d4e5..." }, "cookieHeader": "cf_clearance=v1e3b1f2...; __cf_bm=a1b2c3d4e5..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The cf_clearance clearance cookie value minted by passing the managed challenge | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header. The clearance is bound to both the user agent and the egress IP of the solving session. Prefer the full cookieHeader value over rebuilding a cookie jar from parts — the zone typically pairs cf_clearance with a __cf_bm sibling the edge expects to travel with it. 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 `CloudflareWafSolveRequest`). [API reference](https://www.captcha.fun/docs/api/cloudflare-waf). --- # DataDome: Captcha.fun API > Solve DataDome challenges and replay the `datadome` cookie. ## At a glance - **`kind`**: `datadome` (Captcha.fun's IPs), or `datadome_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `challengeHtml`) - **You get**: datadome cookie → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 the `datadome` cookie value, `cookieHeader`, and `userAgent` for requests to the protected site. - By default the solve targets your URL directly. If your flow uses the invisible-tags variant, pass `challengeHtml` with the captured protected-page HTML. - **Important:** The cookie is bound to the session and IP. If your requests won’t come from the solve’s IP, use a `_proxy` kind with a sticky proxy. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `datadome` · `datadome_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `challengeHtml` | string | no | Captured page HTML for the invisible-tags flow. Omit to use the live slider on `url` | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "datadome_proxy", "url": "https://your-domain.example/protected-page", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "datadome_proxy", "url": "https://your-domain.example/protected-page", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "a1b2c3d4e5f6g7h8i9j0~0~...,", "cookies": { "datadome": "a1b2c3d4e5f6g7h8i9j0~0~...," }, "cookieHeader": "datadome=a1b2c3d4e5f6g7h8i9j0~0~...," } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The trusted datadome cookie value minted by passing the challenge | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin (or token as the bare datadome cookie value), with the returned userAgent as your User-Agent header. The cookie is bound to the solving session, so keep the replay on the same egress IP. 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 `DatadomeSolveRequest`). [API reference](https://www.captcha.fun/docs/api/datadome). --- # DataDome Interstitial: Captcha.fun API > Pass DataDome’s interstitial device check and replay the trusted `datadome` cookie. ## At a glance - **`kind`**: `datadome_interstitial` (Captcha.fun's IPs), or `datadome_interstitial_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `initCookies`) - **You get**: datadome cookie → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 - Handles the full-page interstitial device check (the 403 challenge page) and returns the trusted `datadome` cookie. - Pass `initCookies` if your flow already holds a `datadome` cookie from the tags step. Omit it to start fresh. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `datadome_interstitial` · `datadome_interstitial_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `initCookies` | object | no | Cookies your flow already holds for the protected origin, e.g. { "datadome": "..." }. Optional. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "datadome_interstitial_proxy", "url": "https://your-domain.example/challenge", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "datadome_interstitial_proxy", "url": "https://your-domain.example/challenge", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "b2c3d4e5f6g7h8i9j0k1~0~...,", "cookies": { "datadome": "b2c3d4e5f6g7h8i9j0k1~0~...," }, "cookieHeader": "datadome=b2c3d4e5f6g7h8i9j0k1~0~...," } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The trusted datadome cookie value minted by riding out the interstitial | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `DatadomeInterstitialSolveRequest`). [API reference](https://www.captcha.fun/docs/api/datadome-interstitial). --- # PerimeterX: Captcha.fun API > Solve PerimeterX (HUMAN) block pages and replay the `_px` cookies. ## At a glance - **`kind`**: `perimeterx` (Captcha.fun's IPs), or `perimeterx_proxy` with a `proxy` (your own IP) - **You send**: `url`, `challengeHtml` - **You get**: _px* clearance cookie → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 - Send the captured block-page HTML (the page that loads the PX sensor, `init.js`). Returns the `_px` cookie family. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `perimeterx` · `perimeterx_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `challengeHtml` | string | yes | Captured block-page HTML that loads the PX sensor (`init.js`) | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "perimeterx_proxy", "url": "https://your-domain.example", "challengeHtml": "", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "perimeterx_proxy", "url": "https://your-domain.example", "challengeHtml": "", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8...", "cookies": { "_px3": "d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8...", "_pxhd": "pxhd-sibling..." }, "cookieHeader": "_px3=d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8...; _pxhd=pxhd-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The _px3 clearance cookie value minted by solving the challenge | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `PerimeterxSolveRequest`). [API reference](https://www.captcha.fun/docs/api/perimeterx). --- # PerimeterX Hold: Captcha.fun API > Solve PerimeterX press-and-hold blocks and replay the trusted `_px` cookies. ## At a glance - **`kind`**: `perimeterx_hold` (Captcha.fun's IPs), or `perimeterx_hold_proxy` with a `proxy` (your own IP) - **You send**: `url`, `blockData` (optional: `initCookies`, `verifyUrl`) - **You get**: _px3 and _pxhd cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $3.00 per 1,000 successful solves (30 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 - Send the block payload you received: the XHR JSON block body or the full block-page HTML. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `perimeterx_hold` · `perimeterx_hold_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `blockData` | string | yes | Block payload you received: XHR JSON body or block-page HTML | | `initCookies` | object | no | Cookies you already hold for the site, e.g. `{ "_px": "..." }`. Used as the starting cookie jar | | `verifyUrl` | string | no | URL to validate the new cookies against. Defaults to the block payload’s origin | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "perimeterx_hold_proxy", "url": "https://your-domain.example", "blockData": "", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "perimeterx_hold_proxy", "url": "https://your-domain.example", "blockData": "", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9...", "cookies": { "_px3": "e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9...", "_pxhd": "pxhd-sibling..." }, "cookieHeader": "_px3=e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9...; _pxhd=pxhd-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The trusted _px3 clearance cookie value minted by passing press-and-hold | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `PerimeterxHoldSolveRequest`). [API reference](https://www.captcha.fun/docs/api/perimeterx-hold). --- # PerimeterX Invisible: Captcha.fun API > Solve PerimeterX invisible sensors and replay the `_px` cookies. ## At a glance - **`kind`**: `perimeterx_invisible` (Captcha.fun's IPs), or `perimeterx_invisible_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `jsUrl`) - **You get**: Returned _px cookie family (such as _px3 or _px2); additional cookies vary by target → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 - Works from the target URL; no captured HTML needed. Returns the cookies PerimeterX sets. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `perimeterx_invisible` · `perimeterx_invisible_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `jsUrl` | string | no | Sensor script URL, e.g. `https://client.perimeterx.net//main.min.js`. Found on the page if omitted | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "perimeterx_invisible_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "perimeterx_invisible_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0", "cookies": { "_px3": "f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0", "_pxhd": "pxhd-sibling..." }, "cookieHeader": "_px3=f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0; _pxhd=pxhd-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The primary returned PerimeterX cookie value; the name may be `_px3`, `_px2`, or `_px` | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `PerimeterxInvisibleSolveRequest`). [API reference](https://www.captcha.fun/docs/api/perimeterx-invisible). --- # Akamai (_abck): Captcha.fun API > Get a valid Akamai Bot Manager `_abck` session and replay it. ## At a glance - **`kind`**: `akamai_abck` (Captcha.fun's IPs), or `akamai_abck_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `protectedUrl`) - **You get**: _abck session cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $2.50 per 1,000 successful solves (25 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 - The Akamai _abck product mints a valid sensor session against the target page: the solver runs Akamai's bot manager sensor until the _abck cookie rotates to a valid state, then returns it. - `protectedUrl` names the endpoint your integration will replay against — advisory metadata, not a pre-return validation. The clearance evidence is the in-flow sensor rotation itself; your first replay is the live check. - API-tier enforcement can exceed the cookie family: some storefront APIs answer 403 Access Denied with a cookie that clears the site itself. Treat the first protected-API replay as the live check for your exact endpoint. - **Important:** `_abck` sessions are bound to the IP. Replay from the solve’s IP, or use a `_proxy` kind with a sticky proxy. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `akamai_abck` · `akamai_abck_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `protectedUrl` | string | no | Endpoint you will replay the session against. Defaults to `url`. Advisory — the solver does not validate against it before return | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "akamai_abck_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "akamai_abck_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body.get("protectedUrl") or body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "8AAB1B2C3D4E5F6G7H8I9J0K1L2M3N4O~...", "cookies": { "_abck": "8AAB1B2C3D4E5F6G7H8I9J0K1L2M3N4O~...", "bm_sz": "bm-sz-sibling..." }, "cookieHeader": "_abck=8AAB1B2C3D4E5F6G7H8I9J0K1L2M3N4O~...; bm_sz=bm-sz-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The minted _abck cookie value — the family also carries bm_sz/ak_bmsc/bm_sv siblings | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `AkamaiAbckSolveRequest`). [API reference](https://www.captcha.fun/docs/api/akamai-abck). --- # Akamai (sbsd/bm_sc): Captcha.fun API > Get Akamai `sbsd` / `bm_sc` cookies and replay them. ## At a glance - **`kind`**: `akamai_sbsd` (Captcha.fun's IPs), or `akamai_sbsd_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `protectedUrl`, `funnelUrls`) - **You get**: sbsd and bm_sc cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $2.50 per 1,000 successful solves (25 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 the Akamai `sbsd` and `bm_sc` cookies. Flow URLs have per-site defaults; override them with the request fields below only if your site differs. - Some Akamai gateways answer the protected API with a cpr_chlge sensor challenge that the cookie family alone does not satisfy - the edge wants a sensor POST built from the challenge's t token. Pass protectedUrl to handle those deployments: the solver fires that endpoint from the page after the mint, Akamai's in-page sensor answers any 429 cpr_chlge challenge it draws, and the refreshed family is what returns. Endpoints that never challenge complete with the minted family as before. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `akamai_sbsd` · `akamai_sbsd_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `protectedUrl` | string | no | Endpoint you will replay the session against. Defaults to url. Load-bearing on cpr-challenge deployments: the solver fires it from the page after the mint and returns the post-challenge refreshed family. On endpoints that never challenge, the minted family returns unchanged | | `funnelUrls` | string[] | no | Login-flow URLs to visit, in order, after the `_abck` rotation. Omit for per-site defaults | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "akamai_sbsd_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "akamai_sbsd_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body.get("protectedUrl") or body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "1A2B3C4D5E6F7G8H9I0J~0~...,", "cookies": { "sbsd": "1A2B3C4D5E6F7G8H9I0J~0~...,", "bm_sc": "bm-sc-sibling..." }, "cookieHeader": "sbsd=1A2B3C4D5E6F7G8H9I0J~0~...,; bm_sc=bm-sc-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The minted sbsd cookie value — the sbsd/bm_sc family's load-bearing member | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `AkamaiSbsdSolveRequest`). [API reference](https://www.captcha.fun/docs/api/akamai-sbsd). --- # Akamai (sec_cpt): Captcha.fun API > Solve Akamai `sec_cpt` proof-of-work challenges and replay the cookies. ## At a glance - **`kind`**: `akamai_sec_cpt` (Captcha.fun's IPs), or `akamai_sec_cpt_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `triggerUrl`, `triggerBodyJson`, `protectedUrl`) - **You get**: sec_cpt cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $2.50 per 1,000 successful solves (25 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 - The Akamai sec_cpt product solves the 428 sec-cp-challenge proof-of-work in a real browser: the solver triggers the challenge from the page context, works the proof, and returns the solved sec_cpt cookie plus any Akamai session siblings. - The trigger URL defaults per site; pass triggerUrl with an optional JSON body when your deployment's challenge fires on a specific API call. protectedUrl names the endpoint you will replay against (advisory — not a pre-return validation). ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `akamai_sec_cpt` · `akamai_sec_cpt_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `triggerUrl` | string | no | API URL whose 428 response triggers the challenge. Has per-site defaults | | `triggerBodyJson` | string | no | Raw JSON body for the trigger request — paired with triggerUrl | | `protectedUrl` | string | no | Endpoint you will replay the session against. Defaults to `url`. Advisory — the solver does not validate against it before return | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "akamai_sec_cpt_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "akamai_sec_cpt_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body.get("protectedUrl") or body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "2B3C4D5E6F7G8H9I0J1K~0~...,", "cookies": { "sec_cpt": "2B3C4D5E6F7G8H9I0J1K~0~...,", "bm_sz": "bm-sz-sibling..." }, "cookieHeader": "sec_cpt=2B3C4D5E6F7G8H9I0J1K~0~...,; bm_sz=bm-sz-sibling..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The solved sec_cpt cookie value | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `AkamaiSecCptSolveRequest`). [API reference](https://www.captcha.fun/docs/api/akamai-sec-cpt). --- # Kasada: Captcha.fun API > Get Kasada `x-kpsdk` headers for one protected request. ## At a glance - **`kind`**: `kasada` (Captcha.fun's IPs), or `kasada_proxy` with a `proxy` (your own IP) - **You send**: `url`, `protectedUrl` (optional: `jsDomain`) - **You get**: x-kpsdk-* headers → `headers` - **Default price**: $5.00 per 1,000 successful solves (50 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 - The Kasada product runs the KPSDK sensor (p.js) live until its proof-of-work clears, then fires a request against your protected endpoint from the browser and captures the decorated header set off the wire — the x-kpsdk family plus the cookie header carrying the KP_UID session cookies. - Headers are session- and IP-bound and rotate per request: one solve produces one replayable header set for exactly the endpoint you pass in protectedUrl. - ips.js-only deployments never attach x-kpsdk headers to requests — the edge validates the KP_UID session cookies instead. The sensor type is detected from the page automatically and those sites get the cookie-bearing cleared request; pass the challenge document URL (/fp) as url. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `kasada` · `kasada_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `protectedUrl` | string | yes | Endpoint the headers are for. Replay them on a request to exactly this URL | | `jsDomain` | string | no | Host serving the sensor (`p.js`) when it isn’t on the target’s domain. Omit for standard sites | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "kasada_proxy", "url": "https://your-domain.example", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "kasada_proxy", "url": "https://your-domain.example", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {**res["headers"], "User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"]} target_url = body["protectedUrl"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "", "headers": { "x-kpsdk-ct": "kpsdk:ct:1:...", "x-kpsdk-h": "kpsdk:h:1:...", "x-kpsdk-v": "1.4.0", "cookie": "KP_UIDz=unknown; KP_UIDz_s=unknown" } } ``` | Field | Type | Description | | --- | --- | --- | | `headers` | object | Headers to replay as-is: `x-kpsdk-ct` / `-h` / `-v` plus the `cookie` header with `KP_UID` cookies | 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 Send one outbound request to exactly protectedUrl with every returned header set verbatim — including the cookie header — and the returned userAgent as your User-Agent header. The header set is single-use, session-bound, and IP-bound: mint a fresh solve for every protected request, and keep the replay on the solving session's egress IP. 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 `KasadaSolveRequest`). [API reference](https://www.captcha.fun/docs/api/kasada). --- # Kasada (cd): Captcha.fun API > Get Kasada headers with `x-kpsdk-cd` for one protected request. ## At a glance - **`kind`**: `kasada_cd` (Captcha.fun's IPs), or `kasada_cd_proxy` with a `proxy` (your own IP) - **You send**: `url`, `protectedUrl` (optional: `jsDomain`) - **You get**: x-kpsdk-cd headers → `headers` - **Default price**: $5.00 per 1,000 successful solves (50 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 - For sites that require `x-kpsdk-cd`. Loads the target page live, sends a request to `protectedUrl`, and captures the headers the sensor adds, including `x-kpsdk-cd`. - ips.js-only deployments never attach x-kpsdk headers to requests — the edge validates the KP_UID session cookies instead. The sensor type is detected from the page automatically and those sites get the cookie-bearing cleared request; pass the challenge document URL (/fp) as url. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `kasada_cd` · `kasada_cd_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `protectedUrl` | string | yes | Endpoint the headers are for. Replay them on a request to exactly this URL | | `jsDomain` | string | no | Host serving the sensor (`p.js`) when it isn’t on the target’s domain. Omit for standard sites | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "kasada_cd_proxy", "url": "https://your-domain.example", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "kasada_cd_proxy", "url": "https://your-domain.example", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {**res["headers"], "User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"]} target_url = body["protectedUrl"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "", "headers": { "x-kpsdk-cd": "kpsdk:cd:1:...", "x-kpsdk-ct": "kpsdk:ct:1:...", "x-kpsdk-v": "1.4.0", "cookie": "KP_UIDz=unknown; KP_UIDz_s=unknown" } } ``` | Field | Type | Description | | --- | --- | --- | | `headers` | object | Headers to replay as-is: `x-kpsdk-cd`, the rest of the `x-kpsdk` family, and the `cookie` header with `KP_UID` cookies | 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 Send one outbound request to exactly protectedUrl with every returned header set verbatim — including the cookie header — and the returned userAgent as your User-Agent header. The header set is single-use, session-bound, and IP-bound: mint a fresh solve for every protected request, and keep the replay on the solving session's egress IP. 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 `KasadaCdSolveRequest`). [API reference](https://www.captcha.fun/docs/api/kasada-cd). --- # Kasada (tl payload): Captcha.fun API > Capture a live Kasada `/tl` telemetry request and replay it. ## At a glance - **`kind`**: `kasada_tl_payload` (Captcha.fun's IPs), or `kasada_tl_payload_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `jsDomain`) - **You get**: A replayable /tl request → `body`, `url`, `headers` - **Default price**: $5.00 per 1,000 successful solves (50 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 - Captures the sensor’s own `POST /tl` request from a live session (never synthesized). The /tl body is single-use at Kasada’s edge, so the sensor’s post is answered locally and never delivered — the returned body is fresh, and your replay is its first live posting. Returns the raw body, the request headers, and the exact URL. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `kasada_tl_payload` · `kasada_tl_payload_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `jsDomain` | string | no | Host serving the sensor (`p.js`) when it isn’t on the target’s domain. Omit for standard sites | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "kasada_tl_payload_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "kasada_tl_payload_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry import base64 # POST the captured telemetry request verbatim, from the same egress IP as the capture headers = {**res["headers"], "User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"]} response = requests.post(res["url"], data=base64.b64decode(res["body"]), headers=headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) ``` ## 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": "", "body": "aGVsbG8gd29ybGQ=", "url": "https://your-domain.example/tl", "headers": { "content-type": "application/json", "x-kpsdk-ct": "kpsdk:ct:1:...", "cookie": "KP_UIDz=unknown" } } ``` | Field | Type | Description | | --- | --- | --- | | `body` | string | Base64-encoded raw body of the `POST /tl` request — captured before delivery, so it has never been posted | | `url` | string | Exact URL of the `POST /tl` request. Replay to this URL | | `headers` | object | Request headers, including the `cookie` header with `KP_UID` cookies | 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 POST the decoded body to exactly the returned url with the returned headers verbatim, from the same egress IP as the capture. 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 `KasadaTlPayloadSolveRequest`). [API reference](https://www.captcha.fun/docs/api/kasada-tl-payload). --- # Ticketmaster TMPT: Captcha.fun API > Get a cleared Ticketmaster `tmpt` session and replay it. ## At a glance - **`kind`**: `tmpt` (Captcha.fun's IPs), or `tmpt_proxy` with a `proxy` (your own IP) - **You send**: `url` - **You get**: tmpt session token → `token`, `cookies`, `cookieHeader` - **Default price**: $2.50 per 1,000 successful solves (25 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 - Clears the Kasada check Ticketmaster pages run and returns the `tmpt` token issued once it passes. Event, member, artist, regional, and etickets pages all work; any page that runs the check can be `url`. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `tmpt` · `tmpt_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "tmpt_proxy", "url": "https://www.ticketmaster.com/event/EXAMPLE", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "tmpt_proxy", "url": "https://www.ticketmaster.com/event/EXAMPLE", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "Tm9WbWdPb3N0VGVzdFRva2Vu...", "cookies": { "tmpt": "Tm9WbWdPb3N0VGVzdFRva2Vu...", "KP_UIDz": "unknown" }, "cookieHeader": "tmpt=Tm9WbWdPb3N0VGVzdFRva2Vu...; KP_UIDz=unknown" } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The tmpt cleared-session token the edge mints once the Kasada verdict passes | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on Ticketmaster requests, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `TmptSolveRequest`). [API reference](https://www.captcha.fun/docs/api/tmpt). --- # Imperva: Captcha.fun API > Run Imperva Advanced Bot Protection and replay the clearance cookies. ## At a glance - **`kind`**: `imperva` (Captcha.fun's IPs), or `imperva_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `jsUrl`) - **You get**: Clearance cookies (reese84 plus session cookies) → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 the Imperva Advanced Bot Protection clearance cookie family: the `reese84` cookie plus the session cookies that go with it. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `imperva` · `imperva_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `jsUrl` | string | no | Sensor script URL, if it can’t be found on the page | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "imperva_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "imperva_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "3C4D5E6F7G8H9I0J1K2L~0~...,", "cookies": { "reese84": "3C4D5E6F7G8H9I0J1K2L~0~...,", "incap_ses_1849_949787": "session..." }, "cookieHeader": "reese84=3C4D5E6F7G8H9I0J1K2L~0~...,; incap_ses_1849_949787=session..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The clearance cookie value minted by the sensor run — the reese84 sensor cookie | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `ImpervaSolveRequest`). [API reference](https://www.captcha.fun/docs/api/imperva). --- # Incapsula (_utmvc): Captcha.fun API > Get the Incapsula `_utmvc` cookie and replay it. ## At a glance - **`kind`**: `incapsula_utmvc` (Captcha.fun's IPs), or `incapsula_utmvc_proxy` with a `proxy` (your own IP) - **You send**: `url` - **You get**: _utmvc cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $1.50 per 1,000 successful solves (15 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 the `_utmvc` cookie for the target page. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `incapsula_utmvc` · `incapsula_utmvc_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "incapsula_utmvc_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "incapsula_utmvc_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "4D5E6F7G8H9I0J1K2L3M~0~...,", "cookies": { "_utmvc": "4D5E6F7G8H9I0J1K2L3M~0~...,", "incap_ses_1849_949787": "session..." }, "cookieHeader": "_utmvc=4D5E6F7G8H9I0J1K2L3M~0~...,; incap_ses_1849_949787=session..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The minted _utmvc cookie value — the family also carries incap_ses/visid_incap siblings | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `IncapsulaUtmvcSolveRequest`). [API reference](https://www.captcha.fun/docs/api/incapsula-utmvc). --- # Incapsula (reese84): Captcha.fun API > Get the Incapsula `reese84` cookie and replay it. ## At a glance - **`kind`**: `reese84` (Captcha.fun's IPs), or `reese84_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `jsUrl`) - **You get**: reese84 cookie → `token`, `cookies`, `cookieHeader` - **Default price**: $1.50 per 1,000 successful solves (15 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 the `reese84` cookie for the protected page. The sensor script is located automatically; pass `jsUrl` only when it cannot be found. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `reese84` · `reese84_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `jsUrl` | string | no | Sensor script URL. Found on the page if omitted | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "reese84_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "reese84_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "5E6F7G8H9I0J1K2L3M4N5O~0~...,", "cookies": { "reese84": "5E6F7G8H9I0J1K2L3M4N5O~0~...," }, "cookieHeader": "reese84=5E6F7G8H9I0J1K2L3M4N5O~0~...," } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The minted reese84 cookie value | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Send it as the `reese84` cookie on requests to the protected site, with the returned `userAgent` and from the same IP as the solve. 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 `Reese84SolveRequest`). [API reference](https://www.captcha.fun/docs/api/reese84). --- # AWS WAF: Captcha.fun API > Solve AWS WAF invisible challenges and replay the `aws-waf-token` cookie. ## At a glance - **`kind`**: `aws_waf` (Captcha.fun's IPs), or `aws_waf_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `jsUrl`) - **You get**: aws-waf-token cookie → `token` - **Default price**: $2.00 per 1,000 successful solves (20 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 - Passes the AWS WAF invisible challenge and returns the `aws-waf-token`, together with the user agent of the solve. - The challenge script is located automatically from the page. Pass `jsUrl` only for deployments whose page never references it, such as geo-blocked shells or API-only integrations. - **Important:** Send the token as the `aws-waf-token` cookie, not an `x-aws-waf-token` header. Real browsers never send that header. Tokens are reusable for a few minutes, then the edge re-challenges (202/405) — solve again when that happens. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `aws_waf` · `aws_waf_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `jsUrl` | string | no | Challenge script URL (`*.token.awswaf.com`, `*.edge.sdk.awswaf.com`, or a first-party `/__challenge_*` path). Found on the page if omitted | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "aws_waf", "url": "https://your-domain.example" } ``` ## 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": "aws_waf", "url": "https://your-domain.example", } 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"] # aws-waf-token cookie ``` ## 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": "a1B2c3D4e5F6g7H8i9J0-K1L2M3..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | `aws-waf-token` value. Send it as a cookie | 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 Send it as the `aws-waf-token` cookie with the returned `userAgent` and from the same IP as the solve. 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 `AwsWafSolveRequest`). [API reference](https://www.captcha.fun/docs/api/aws-waf). --- # Shape: Captcha.fun API > Get Shape Security headers for one protected request. ## At a glance - **`kind`**: `shape` (Captcha.fun's IPs), or `shape_proxy` with a `proxy` (your own IP) - **You send**: `url`, `challengeHtml`, `protectedUrl` - **You get**: Shape request headers → `headers` - **Default price**: $5.00 per 1,000 successful solves (50 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 - The Shape product replays the page that loads Shape's sensor (ssx.mod.js or an inline equivalent), lets the sensor arm, then fires a request against your protected endpoint from the browser and harvests the Shape headers off the wire. - Shape headers are URL-bound — they encode the request URL into their telemetry — so one solve produces one replayable header set for exactly the endpoint you pass in protectedUrl. Mint a fresh header set per protected endpoint. - A few gateways reject synthetic captures at the app layer; the solver detects those protected URLs and drives the site's real sign-in UI to mint the set instead. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `shape` · `shape_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `challengeHtml` | string | yes | Captured HTML of the page that loads Shape’s sensor | | `protectedUrl` | string | yes | Endpoint the headers are for. Replay them on a request to exactly this URL | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "shape_proxy", "url": "https://your-domain.example/login", "challengeHtml": "", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "shape_proxy", "url": "https://your-domain.example/login", "challengeHtml": "", "protectedUrl": "https://your-domain.example/api/login", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {**res["headers"], "User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"]} target_url = body["protectedUrl"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "", "headers": { "x-6a7b8c-a": "0:0:0:0:1:2:3:...", "x-6a7b8c-b": "1:1:1:...", "x-6a7b8c-f": "abc123..." } } ``` | Field | Type | Description | | --- | --- | --- | | `headers` | object | Shape headers to replay as-is: the `x--a` / `a0` / `b` / `c` / `d` / `f` / `z` family plus extras like `x-application-mouse-tool-key` | 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 Send one outbound request to exactly protectedUrl with every returned header set verbatim, and the returned userAgent as your User-Agent header. Replay the set from the same egress IP as the solve, with the returned userAgent as your User-Agent header — the headers are bound to the solving browser's identity. 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 `ShapeSolveRequest`). [API reference](https://www.captcha.fun/docs/api/shape). --- # Castle: Captcha.fun API > Get one-time Castle request tokens for protected calls. ## At a glance - **`kind`**: `castle` (Captcha.fun's IPs), or `castle_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `castlePk`) - **You get**: One-time X-Castle-Request-Token → `token` - **Default price**: $2.00 per 1,000 successful solves (20 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 the token the Castle SDK generates: the `X-Castle-Request-Token` header value for login, register, or transaction calls. - The publishable key is read from the page. Pass `castlePk` only if it isn’t in the page (bundled or loaded later). - **Important:** Tokens are single-use and expire quickly. Don’t cache or batch them; solve once per protected call. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `castle` · `castle_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `castlePk` | string | no | Castle publishable key (`pk_...`). Read from the page if omitted | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "castle", "url": "https://your-domain.example/login" } ``` ## 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": "castle", "url": "https://your-domain.example/login", } 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"] # One-time X-Castle-Request-Token ``` ## 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": "eyJhbGciOiJIUzI1NiJ9.eyJzaWQiOiI..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | One-time `X-Castle-Request-Token` value | 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 Send the token as the `X-Castle-Request-Token` header of the protected call. It expires in about two minutes, so use it right away. The token embeds the solving session's fingerprint — replay it from the same egress IP as the solve, with the returned userAgent as your User-Agent header. 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 `CastleSolveRequest`). [API reference](https://www.captcha.fun/docs/api/castle). --- # Forter: Captcha.fun API > Get Forter tokens and replay them on the site’s next request. ## At a glance - **`kind`**: `forter` (Captcha.fun's IPs), or `forter_proxy` with a `proxy` (your own IP) - **You send**: `url` (optional: `forterSiteId`, `jsUrl`) - **You get**: Forter token for the next request → `token` - **Default price**: $2.00 per 1,000 successful solves (20 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 - The Forter product runs the site's Forter loader live and returns the token the protected flow expects on its next request. - The site id and JS bundle are discovered from the page; forterSiteId and jsUrl are optional annotations for deployments where they are known and not statically discoverable. - CDN-alias deployments whose loader host never appears on the page do not boot without jsUrl - pass it for that class of site. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `forter` · `forter_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `forterSiteId` | string | no | Forter site ID. Found on the page if omitted | | `jsUrl` | string | no | Forter script URL. Found on the page if omitted | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "forter", "url": "https://your-domain.example" } ``` ## 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": "forter", "url": "https://your-domain.example", } 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"] # Forter token for the next request ``` ## 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": "6F7G8H9I0J1K2L3M4N5O6P7Q8..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Forter token for the site’s next request | 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 Send the token where the site expects it, with the returned `userAgent` and from the same IP as the solve. Tokens are short-lived, so use them right away. 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 `ForterSolveRequest`). [API reference](https://www.captcha.fun/docs/api/forter). --- # Riskified: Captcha.fun API > Collect Riskified beacon cookies and replay them. ## At a glance - **`kind`**: `riskified` (Captcha.fun's IPs), or `riskified_proxy` with a `proxy` (your own IP) - **You send**: `url` - **You get**: Riskified cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $2.00 per 1,000 successful solves (20 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 the Riskified cookies (`rCookie`, `rskxRunCookie`, `lastRskxRun`; names vary by merchant). ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `riskified` · `riskified_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "riskified_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "riskified_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "", "cookies": { "rCookie": "7G8H9I0J1K2L3M4N5O6P7Q8R9", "rskxRunCookie": "rskx-run..." }, "cookieHeader": "rCookie=7G8H9I0J1K2L3M4N5O6P7Q8R9; rskxRunCookie=rskx-run..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Empty — Riskified's merchant-specific family has no single load-bearing cookie; replay cookies/cookieHeader instead | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the merchant origin, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. 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 `RiskifiedSolveRequest`). [API reference](https://www.captcha.fun/docs/api/riskified). --- # ThreatMetrix: Captcha.fun API > Bind a ThreatMetrix device profile to your session ID. ## At a glance - **`kind`**: `threatmetrix` (Captcha.fun's IPs), or `threatmetrix_proxy` with a `proxy` (your own IP) - **You send**: `url`, `threatMetrixOrgId`, `threatMetrixSessionId` - **You get**: Your session ID, bound to a profile → `token` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Runs ThreatMetrix device profiling and binds the profile to the session ID you supply. No domain input is needed. - There’s no token to replay: after the solve, send the same session ID with your protected call. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `threatmetrix` · `threatmetrix_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `threatMetrixOrgId` | string | yes | Your ThreatMetrix org ID: the account the profile binds to | | `threatMetrixSessionId` | string | yes | Session ID to bind the profile to. Send the same ID with the protected call | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "threatmetrix", "url": "https://your-domain.example", "threatMetrixOrgId": "YOUR_THREATMETRIX_ORG_ID", "threatMetrixSessionId": "YOUR_TRANSACTION_SESSION_ID" } ``` ## 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": "threatmetrix", "url": "https://your-domain.example", "threatMetrixOrgId": "YOUR_THREATMETRIX_ORG_ID", "threatMetrixSessionId": "YOUR_TRANSACTION_SESSION_ID", } 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"] # Your session ID, bound to a profile ``` ## 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": "YOUR_TRANSACTION_SESSION_ID" } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | Your session ID, echoed once the profile is bound | 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 Send the same session ID with the protected login or payment call. ThreatMetrix scores it against the profile bound during the solve. The profile is bound to the solving browser's fingerprint and egress IP — send the protected call from the same egress. Profiles accumulate trust server-side; a session id solved seconds ago starts with minimal history. 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 `ThreatmetrixSolveRequest`). [API reference](https://www.captcha.fun/docs/api/threatmetrix). --- # Vercel Challenge: Captcha.fun API > Pass Vercel’s attack challenge and replay the clearance cookies. ## At a glance - **`kind`**: `vercel_challenge` (Captcha.fun's IPs), or `vercel_challenge_proxy` with a `proxy` (your own IP) - **You send**: `url` - **You get**: Clearance cookies → `token`, `cookies`, `cookieHeader` - **Default price**: $1.50 per 1,000 successful solves (15 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 - Passes Vercel's attack-challenge-mode page and returns the clearance cookie. ## Request: `POST /v1/request` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | `vercel_challenge` · `vercel_challenge_proxy` | | `url` | string | yes | Page URL where the challenge appears. | | `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: `, `Content-Type: application/json`; optional `APP_ID` to tag requests. Example body: ```json { "kind": "vercel_challenge_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD" } ``` ## 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. Set `CAPTCHAFUN_PROXY` to the same sticky HTTP proxy used for the solve and replay: `host:port` or `host:port:username:password`. Keep its egress IP stable. ```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": "vercel_challenge_proxy", "url": "https://your-domain.example", "proxy": "proxy.example:8080:USERNAME:PASSWORD", } from urllib.parse import quote proxy = os.environ["CAPTCHAFUN_PROXY"] parts = proxy.split(":") if len(parts) not in (2, 4): raise ValueError("CAPTCHAFUN_PROXY must be host:port or host:port:username:password") credentials = f"{quote(parts[2], safe='')}:{quote(parts[3], safe='')}@" if len(parts) == 4 else "" proxy_url = f"http://{credentials}{parts[0]}:{parts[1]}" proxies = {"http": proxy_url, "https": proxy_url} body["proxy"] = proxy solve_id, res = solve(body) if not res["success"]: raise RuntimeError(res.get("message", "solve failed")) # not billed; create a new solve to retry # Replay through the same sticky proxy, preserving the returned client headers. headers = {"User-Agent": res["userAgent"], "Sec-CH-UA": res["secUa"], "Cookie": res["cookieHeader"]} target_url = body["url"] def replay(method, request_body=None, *, content_type=None): request_headers = dict(headers) if content_type is not None: request_headers["Content-Type"] = content_type response = requests.request(method, target_url, data=request_body, headers=request_headers, proxies=proxies, timeout=30) if response.status_code >= 400: raise requests.HTTPError(f"Target returned HTTP {response.status_code} (solve ID: {solve_id}); inspect error.response and report this ID if the solve result was rejected", response=response) return response # Choose your endpoint's method and body before calling replay. # For JSON bytes, explicitly select application/json: # response = replay("POST", request_body=b'{"replace": "YOUR_PAYLOAD"}', content_type="application/json") # Inspect the response body too: an HTTP 200 can still indicate a rejected result. ``` ## 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": "8H9I0J1K2L3M4N5O6P7Q8R9S0...", "cookies": { "_vcrcs": "8H9I0J1K2L3M4N5O6P7Q8R9S0..." }, "cookieHeader": "_vcrcs=8H9I0J1K2L3M4N5O6P7Q8R9S0..." } ``` | Field | Type | Description | | --- | --- | --- | | `token` | string | The _vcrcs clearance cookie value minted by passing the attack-mode checkpoint | | `cookies` | object | The defense cookie family, cookie name to value | | `cookieHeader` | string | The solving session's full Cookie header value, verbatim — replay it with the returned userAgent from the same egress IP | 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 Replay cookieHeader as the Cookie header on outbound requests to the protected deployment, with the returned userAgent as your User-Agent header, from the same egress IP as the solve. Keep your other request headers close to a real browser's — the clearance binds to the solving session's header/TLS fingerprint, not just the user agent and IP. 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 `VercelChallengeSolveRequest`). [API reference](https://www.captcha.fun/docs/api/vercel-challenge).