{"openapi":"3.1.0","info":{"title":"Captcha Solver","description":"\nLocal captcha-solving HTTP sidecar built on **CloakBrowser** (self-hosted anti-detect\nChromium). Solves challenges by driving them in a real browser engine.\n\n**Supported (11):** Turnstile · reCAPTCHA (v2/v3/invisible, Enterprise) · hCaptcha ·\nCloudflare (`cf_clearance`) · AWS WAF · BotGuard · DataDome · PerimeterX · Akamai ·\nAliyun · **Arkose FunCaptcha** (ONNX; needs a real page that fires `/fc/gfct/`).\n\nDispatch is by the `type` field of `POST /solve`. Optional fields select the variant\n(`version`, `real_page`, `verify_url`, `public_key`, …).\n\n**Docs surfaces (all public):**\n- `/docs` — Swagger UI (Authorize + Try it out)\n- `/redoc` — ReDoc\n- `/openapi.json` — raw OpenAPI 3\n- `/api-docs` — operator HTML reference\n- `/` → `/ui` — dashboard\n\nBehind the public domain, nginx enforces Bearer on `/solve`, `/status`, `/logs`\n(and similar). Localhost has no auth.\n\nCaller-supplied URLs (`url`, `verify_url`, `page_url`, `post_fetch[].url`) are fetched\nfrom the browser session and are **SSRF-guarded**: private/loopback/link-local targets\nare rejected unless `SOLVER_ALLOW_PRIVATE=1`.\n\n**Honest limits:** reCAPTCHA v2 image is MIXED (not production-ready on free path).\nArkose models: 24 ONNX files on disk from `https://funcaptchamodel.unix.do` (23/24\nloadable; missing `threed_rollball_animal.onnx`). Full Arkose E2E needs a live\ntarget that emits `gfct` — public demos usually do not.\n","contact":{"name":"solver","url":"https://ircaptcha.isrealllairdrop.net/"},"version":"1.0.0"},"servers":[{"url":"https://ircaptcha.isrealllairdrop.net","description":"Public (Bearer token required)"},{"url":"http://127.0.0.1:8877","description":"Local (no auth)"}],"paths":{"/health":{"get":{"tags":["monitoring"],"summary":"Liveness + supported types (public, no auth)","description":"Public liveness probe. Lists the captcha types this service can solve.","operationId":"health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/vectors":{"get":{"tags":["demo"],"summary":"Public demo test vectors (no auth)","description":"Curated public demo vectors for quick testing in the UI.\nAll entries are from verified public demos (peet.ws, clifford.io, official sites).\nSafe to use for verification.","operationId":"get_vectors_vectors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/stats":{"get":{"tags":["monitoring"],"summary":"Aggregated solve statistics from ring buffer","description":"Lightweight analytics over the last ~100 solve events.\nPer-type success rate, count, average elapsed time.","operationId":"getStats","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/solve":{"post":{"tags":["solve"],"summary":"Solve a captcha (dispatch by type)","description":"Solve any supported captcha and return the token.\n\nDispatch is by `type`; the variant is selected by optional fields:\n\n- **Turnstile** — default route-intercept; `verify_url`+`verify_payload` to\n  solve-and-verify; `real_page:true` to drive the live page (pre_actions/post_fetch).\n- **reCAPTCHA** — `version`: `v2` (checkbox + Mistral image fallback, `real_page` supported),\n  `v3` (score; pass `secret` to also return the score), `invisible`. `enterprise:true`\n  for Enterprise keys.\n- **hCaptcha** — default checkbox (Mistral image/drag fallback); `action:\"invisible\"`\n  for the execute path; `real_page:true` for the live page.\n- **cloudflare** — pass the full-page Cloudflare interstitial (Managed or JS challenge)\n  and return the `cf_clearance` cookie + `user_agent` + all cookies. No `sitekey`;\n  pass `proxy` so the cookie is bound to a replayable IP. See the README for the\n  replay contract (IP + JA3 + UA must match).\n- **awswaf** — navigate an AWS-WAF-protected URL, let the silent JS challenge set\n  `aws-waf-token`, and return it + `user_agent` + all cookies. No `sitekey`; pass\n  `proxy` (same IP-bound replay contract as cloudflare). Silent challenge only —\n  no interactive visual-puzzle support.\n\n**Success signal:** every response carries a uniform top-level `solved` bool — read\nit and don't branch per-type. Type-specific detail still rides along (`token`,\n`cf_clearance`, `score`, `expires_in`, `cookies`, `user_agent`, `post_fetch`, …).\n\n**Error contract (two rules):** a solve that ran but didn't succeed returns **200**\nwith `solved:false` + `error` set. A **4xx/5xx** means the request never solved —\nFastAPI's `{detail}` envelope (400 bad input, 408 timeout, 422 schema, 500 crash).\nSo: 2xx → read `solved`; non-2xx → read `detail`. Never both.","operationId":"solve","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolveRequest"},"examples":{"turnstile":{"summary":"Turnstile (route-intercept)","value":{"type":"turnstile","sitekey":"0x4AAAAAAA...","url":"https://target.com"}},"recaptcha_v3":{"summary":"reCAPTCHA v3 Enterprise (score)","value":{"type":"recaptcha","version":"v3","enterprise":true,"sitekey":"6Lc...","url":"https://target.com","action":"login"}},"recaptcha_v2":{"summary":"reCAPTCHA v2 checkbox","value":{"type":"recaptcha","version":"v2","sitekey":"6Lf...","url":"https://target.com/form"}},"hcaptcha":{"summary":"hCaptcha (checkbox)","value":{"type":"hcaptcha","sitekey":"10000000-ffff-ffff-ffff-000000000001","url":"https://target.com"}},"turnstile_realpage":{"summary":"Turnstile on the live page (pre_actions + post_fetch)","value":{"type":"turnstile","real_page":true,"url":"https://app.example.com/login","pre_actions":[{"type":"fill","selector":"input[type=email]","value":"u@ex.com"},{"type":"click","selector":"button[type=submit]"}],"post_fetch":[{"url":"https://app.example.com/api/verify","body":{"token":"__TOKEN__"}}]}},"cloudflare_clearance":{"summary":"Cloudflare clearance (cf_clearance — Managed or JS challenge)","value":{"type":"cloudflare","url":"https://protected.example.com","proxy":"http://user:pass@ip:port"}},"aws_waf":{"summary":"AWS WAF token (silent JS challenge → aws-waf-token)","value":{"type":"awswaf","url":"https://protected.example.com/waitlist","proxy":"http://user:pass@ip:port"}},"botguard":{"summary":"BotGuard (Google OAuth bgRequest token + session cookies)","value":{"type":"botguard","email":"user@example.com","password":"optional-for-hard-gate-token"}},"datadome":{"summary":"DataDome clearance cookie — caller passes the DataDome-fronted url (+ referer)","value":{"type":"datadome","url":"https://octocaptcha.com/datadome?origin_page=github_signup_redesign","referer":"https://github.com/","proxy":"http://user:pass@ip:port"}},"akamai":{"summary":"Harvest an Akamai Bot Manager _abck clearance cookie (caller passes the Akamai-fronted url)","value":{"type":"akamai","url":"https://www.example-akamai-site.com/","proxy":"http://user:pass@ip:port"}},"perimeterx":{"summary":"PerimeterX/HUMAN 'Press & Hold' → harvest _px3 clearance cookie (render_flow trigger)","value":{"type":"perimeterx","render_flow":"outlook_signup","proxy":"http://user:pass@ip:port"}},"arkose":{"summary":"Arkose FunCaptcha (ONNX image prediction — needs real page that fires gfct)","value":{"type":"arkose","public_key":"A0DE7B75-1138-44F2-B132-ED188CEB66F3","url":"https://login.example.com/login","timeout_s":120,"proxy":"http://user:pass@ip:port"}}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolveResponse"}}}},"400":{"description":"Bad request — unsupported type, missing sitekey for a widget type, or an SSRF-rejected URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"Global deadline (timeout_s) exceeded before a result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unhandled solver error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/logs":{"get":{"tags":["monitoring"],"summary":"Recent solve events (ring buffer)","description":"Last N solve events (max 200). Tokens are recorded as a boolean, never stored.\n`total` is the full ring-buffer size; `logs` is the requested slice of it.","operationId":"getLogs","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lines","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"How many recent events (max 200)","default":50,"title":"Lines"},"description":"How many recent events (max 200)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/status":{"get":{"tags":["monitoring"],"summary":"Service status + currently running tasks","description":"Per-type online status and the list of in-flight solve tasks.","operationId":"status","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message"}},"type":"object","required":["detail"],"title":"ErrorResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","examples":["ok"]},"supported_types":{"items":{"type":"string"},"type":"array","title":"Supported Types","examples":[["turnstile","recaptcha","hcaptcha"]]}},"type":"object","required":["status","supported_types"],"title":"HealthResponse"},"LogsResponse":{"properties":{"logs":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Logs"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["logs","total"],"title":"LogsResponse"},"PostFetch":{"properties":{"url":{"type":"string","title":"Url","description":"Endpoint to call (SSRF-guarded, same as top-level url)","examples":["https://target.com/api/verify"]},"method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Method","default":"POST","examples":["POST"]},"body":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body","description":"JSON body. Use the literal __TOKEN__ anywhere to inject the solved token.","examples":[{"token":"__TOKEN__"}]}},"type":"object","required":["url"],"title":"PostFetch","description":"An API call fired from the SAME browser session after solving."},"PreAction":{"properties":{"type":{"type":"string","title":"Type","description":"click | fill | select | press | wait","examples":["click"]},"selector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selector","description":"Target selector. Formats: CSS (default), XPath (//…), text=…, regex=…, role=name[name='…']","examples":["text=Continue with Email"]},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value","description":"Value for fill/select/press, or seconds for wait"},"timeout":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout","description":"Element wait timeout (ms)","default":10000}},"type":"object","required":["type"],"title":"PreAction","description":"One UI step to run before the captcha appears (real_page mode)."},"SolveRequest":{"properties":{"type":{"type":"string","title":"Type","description":"Captcha type — dispatch key.","examples":["turnstile"]},"sitekey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sitekey","description":"Site key from the target page. Required for turnstile/recaptcha/hcaptcha; not used for type=cloudflare (page-level clearance).","examples":["0x4AAAAAAA..."]},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Page the captcha is on (also the intercept origin). Required for all types except botguard (which defaults to the Google sign-in page).","examples":["https://target.com"]},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"Turnstile action, or reCAPTCHA v3/invisible action. For hCaptcha, the literal \"invisible\" selects the invisible-execute path."},"cdata":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cdata","description":"Turnstile customer data bound into the token."},"real_page":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Real Page","description":"Solve on the live target page (navigate + drive) instead of a stub.","default":false},"timeout_s":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout S","description":"Overall solve deadline (seconds). Enforced server-side; on expiry the call returns 408 and the browser is released.","default":60},"pre_actions":{"anyOf":[{"items":{"$ref":"#/components/schemas/PreAction"},"type":"array"},{"type":"null"}],"title":"Pre Actions","description":"Steps to run before solving (real_page)."},"post_fetch":{"anyOf":[{"items":{"$ref":"#/components/schemas/PostFetch"},"type":"array"},{"type":"null"}],"title":"Post Fetch","description":"API calls after solving (real_page)."},"proxy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy","description":"Per-request proxy (scheme://user:pass@host:port). Honored for type=cloudflare and type=awswaf (overrides the shared TURNSTILE_PROXY env fallback); their cookies are IP-bound, so replay from this same proxy IP. For turnstile/recaptcha set TURNSTILE_PROXY / RECAPTCHA_PROXY instead — the per-request field is not wired for those."},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version","description":"reCAPTCHA only: v2 | v3 | invisible (default v2)."},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"reCAPTCHA v3 only: target's secret key, to also return the score."},"enterprise":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enterprise","description":"reCAPTCHA only: load enterprise.js / grecaptcha.enterprise.","default":false},"verify_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verify Url","description":"Turnstile: verify the token from the same session at this URL."},"verify_payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Verify Payload","description":"Turnstile: body for verify_url; token is injected as \"token\"."},"page_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Url","description":"Turnstile: origin to intercept (defaults to verify_url)."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"BotGuard: account email to enter — drives the sign-in flow to the token-bearing RPC."},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"BotGuard: optional password — if set, drives to the password step and grabs the B4hajb hard-gate token instead of the MI613e lookup token."},"referer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referer","description":"datadome: optional framing Referer so DataDome serves the same config/scoring as the real flow. The caller supplies its own site's referer (e.g. https://github.com/ when harvesting via octocaptcha). Pair with a `url` pointing at the DataDome-fronted page that loads tags.js."},"render_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Render Flow","description":"perimeterx: named site trigger that makes the gate render when it doesn't show on plain load (default 'outlook_signup'). Throwaway navigation only — NOT account creation. Pass null with a `url` for deployments whose gate renders on goto(). Harvests the _px3 clearance cookie (bound to _pxvid+IP+UA; replay under the same proxy+UA within TTL)."},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id","description":"aliyun: the SceneId of the target site's captcha (e.g. read from the page config). Required for type=aliyun."},"prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prefix","description":"aliyun: the captcha-open endpoint prefix (e.g. '13lbkb5' -> <prefix>.captcha-open-southeast.aliyuncs.com). Required for type=aliyun."},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"aliyun: captcha region — 'sgp' (default), 'cn', or 'intl'."},"public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Key","description":"arkose: Arkose public key from the target site's embed. Required for type=arkose."},"game_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Game Type","description":"arkose: Arkose game type hint (default '4'). Live game_type is still taken from gfct when present.","default":"4"}},"type":"object","required":["type"],"title":"SolveRequest"},"SolveResponse":{"properties":{"type":{"type":"string","title":"Type","description":"Echoes the request type — the dispatch discriminator.","examples":["turnstile"]},"solved":{"type":"boolean","title":"Solved","description":"THE success signal. True iff the captcha was solved, uniform across every type — read this instead of branching per-type."},"token":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Token","description":"Solved token for token types (turnstile/recaptcha/hcaptcha). Absent for type=cloudflare (see cf_clearance); empty string on a failed/realpage solve — trust `solved`, not this. aliyun returns a dict {sceneId, certifyId, deviceToken, data}."},"method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Method","description":"Which path solved it (route | execute | real-page | image | …)."},"elapsed":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elapsed","description":"Solve time (seconds)."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Set when the solve failed but returned 200."},"verify_success":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Verify Success","description":"realpage variants: token harvested + verified."},"success":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Success","description":"Page-level (cloudflare/awswaf): cookie obtained."},"cf_clearance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cf Clearance","description":"type=cloudflare: the cf_clearance cookie record."}},"additionalProperties":true,"type":"object","required":["type","solved"],"title":"SolveResponse"},"StatusResponse":{"properties":{"services":{"additionalProperties":{"type":"string"},"type":"object","title":"Services"},"current":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Current","description":"Currently running solve tasks."}},"type":"object","required":["services","current"],"title":"StatusResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","description":"Bearer token (required on the public domain; enforced by nginx). Ignored for local calls.","scheme":"bearer"}}},"tags":[{"name":"solve","description":"Solve a captcha challenge."},{"name":"monitoring","description":"Liveness, current tasks, recent solve log."}]}