# funsolver API — reference for AI agents > Self-hosted, 2captcha-compatible captcha-solving API. Submit a task with > createTask, then poll getTaskResult until it is ready. A single endpoint > accepts every captcha type; the kind is chosen by the task.type field. Base URL: https://api.funsolver.com Auth: include "clientKey" in every request body (create one in the dashboard → API Keys). No auth headers. Content-Type: application/json for all POSTs. ## Request lifecycle 1. POST /createTask { clientKey, task: { type, ... } } -> { errorId: 0, taskId } 2. POST /getTaskResult { clientKey, taskId } -> { status: "processing" } while solving 3. Repeat step 2 every 1-2s until { status: "ready", solution: { ... } } or errorId != 0. Balance is charged only on a successful solve. ## Endpoints POST /createTask submit a task -> { errorId, taskId } POST /getTaskResult fetch a result by taskId -> { errorId, status, solution? } POST /getBalance { clientKey } -> { errorId, balance } GET /health liveness + counters ## getTaskResult responses processing: { "errorId": 0, "status": "processing" } ready: { "errorId": 0, "status": "ready", "solution": { ... }, "processingTime": 4.2 } error: { "errorId": 1, "errorCode": "ERROR_...", "errorDescription": "..." } ## Task types Field names below are literal JSON keys inside the "task" object. ### FunCaptcha task.type: "FunCaptchaTask" Returns a FunCaptcha token for the given site. Parameters: - websiteURL (required): Page URL where the captcha is shown. - websitePublicKey (required): FunCaptcha site key (publicKey). - proxy (required): http://user:pass@host:port (HTTP/HTTPS or SOCKS5). - funcaptchaApiJSSubdomain (optional): api.js subdomain, e.g. blizzard-api.arkoselabs.com. Optional. - data (optional): Trigger blob, raw string or as {"blob":"..."}. Required for some sites. - userAgent (optional): UA used while solving. Picked from a pool when omitted. - cookies (optional): Cookies as "name=val; name2=val2". solution: { token, userAgent } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "FunCaptchaTask", "websiteURL": "https://account.battle.net/recovery/ru/identify-account.html", "websitePublicKey": "9C3260E3-AD4C-6719-E0A9-56D21BFB0BCD", "proxy": "http://user:pass@host:port" } } ``` ### Castle task.type: "CastleTask" Returns a Castle request token. Target domain must be on the allow-list. Parameters: - websiteURL (required): URL of the Castle-protected page. - proxy (required): http://user:pass@host:port (HTTP/HTTPS or SOCKS5). Solves run under the client proxy only — no server-side pool. - cookies (optional): Optional cookies — array of { name, value, domain, path }. solution: { token, userAgent, acceptLang, secChUa, secChUaPlatform, cookies /* replay ALL five header fields + the cookie jar verbatim — UA / sec-ch-ua mismatch invalidates the token immediately */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "CastleTask", "websiteURL": "https://example.com/protected-page", "proxy": "http://user:pass@host:port" } } ``` ### reCAPTCHA v2 task.type: "RecaptchaV2Task" Checkbox or invisible reCAPTCHA v2. Returns g-recaptcha-response (~2 min lifetime). Token is bound to the websiteURL domain. Parameters: - websiteURL (required): Full URL of the page hosting the reCAPTCHA. - websiteKey (required): reCAPTCHA site key (data-sitekey / "k" in /recaptcha/api2/anchor). Alias: websitePublicKey. - proxy (required): Residential proxy: http://user:pass@host:port, socks5://user:pass@host:port or host:port:user:pass. - isInvisible (optional): true for invisible reCAPTCHA. - pageAction (optional): Value of "sa" in /recaptcha/api2/anchor, if present. - recaptchaDataSValue (optional): Value of "s" in /recaptcha/api2/anchor (data-s), if present — without it the token is rejected. - apiDomain (optional): "www.google.com" (default) or "www.recaptcha.net". solution: { token /* g-recaptcha-response, ~2 min */, userAgent /* submit with this UA through the same proxy */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "RecaptchaV2Task", "websiteURL": "https://example.com/login", "websiteKey": "6Lc_aCMTAAAAA...", "proxy": "http://user:pass@host:port" } } ``` ### reCAPTCHA v2 Enterprise task.type: "RecaptchaV2EnterpriseTask" Same as RecaptchaV2Task for Enterprise keys (site loads enterprise.js, requests go to /recaptcha/enterprise/…). Parameters: - websiteURL (required): Full URL of the page hosting the reCAPTCHA Enterprise widget. - websiteKey (required): Enterprise site key ("k" in /recaptcha/enterprise/anchor). Alias: websitePublicKey. - proxy (required): Residential proxy: http://user:pass@host:port, socks5://user:pass@host:port or host:port:user:pass. - isInvisible (optional): true for invisible reCAPTCHA. - pageAction (optional): Value of "sa" in /recaptcha/enterprise/anchor (e.g. LOGIN), if present. - enterprisePayload (optional): Extra widget params; if /recaptcha/enterprise/anchor has "s", pass {"s": ""}. - apiDomain (optional): "www.google.com" (default) or "www.recaptcha.net". solution: { token /* g-recaptcha-response, ~2 min */, userAgent /* submit with this UA through the same proxy */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "RecaptchaV2EnterpriseTask", "websiteURL": "https://example.com/login", "websiteKey": "6Lc_aCMTAAAAA...", "proxy": "http://user:pass@host:port" } } ``` ### reCAPTCHA v3 task.type: "RecaptchaV3Task" Returns a token and score (0.0–1.0). Score is never guaranteed; cleaner IP/proxy → higher score. Parameters: - websiteURL (required): Page that runs reCAPTCHA v3. - websitePublicKey (required): reCAPTCHA v3 site key. - action (optional): Action the site expects (e.g. login). Defaults to "verify". - min_score (optional): Desired minimum score 0.0–1.0 (hint only). - proxy (required): http://user:pass@host:port (HTTP/HTTPS or SOCKS5). solution: { token, score, userAgent } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "RecaptchaV3Task", "websiteURL": "https://example.com/", "websitePublicKey": "6Lc_aCMTAAAAA...", "action": "verify", "min_score": 0.7, "proxy": "http://user:pass@host:port" } } ``` ### reCAPTCHA v3 Enterprise task.type: "RecaptchaV3EnterpriseTask" Same as RecaptchaV3Task, but for enterprise sitekeys — enterprise.js + grecaptcha.enterprise.execute. Parameters: - websiteURL (required): Page that runs reCAPTCHA v3 Enterprise. - websitePublicKey (required): reCAPTCHA v3 Enterprise site key. - action (optional): Action the site expects. Defaults to "verify". - min_score (optional): Desired minimum score 0.0–1.0 (hint only). - proxy (required): http://user:pass@host:port (HTTP/HTTPS or SOCKS5). solution: { token, score, userAgent } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "RecaptchaV3EnterpriseTask", "websiteURL": "https://example.com/", "websitePublicKey": "6Lc_aCMTAAAAA...", "action": "verify", "min_score": 0.7, "proxy": "http://user:pass@host:port" } } ``` ### hCaptcha task.type: "HCaptchaTask" Returns an h-captcha-response token (P1_...). Reliably supported: Steam (help.steampowered.com, steamcommunity.com — checkbox widget) and E*** G**** (target-site.com — invisible enterprise via rqdata); other domains in progress. For target-site, pass rqdata plus the same proxy used to obtain it (enterprise is bound to the session IP). Submit the token under the returned userAgent — a UA mismatch drops the score to 0. Token lives ~120s. Parameters: - websiteURL (required): Page hosting the hCaptcha widget (domain context for the sitekey). - websitePublicKey (required): hCaptcha site key (widget data-sitekey). - proxy (optional): http://user:pass@host:port (HTTP/HTTPS or SOCKS5). For enterprise the IP must match the rqdata session. Omit for HCaptchaTaskProxyless (our IP). - rqdata (optional): Enterprise payload (rqdata) generated by the site in your session. Required for enterprise sitekeys; submit the token in the same session the rqdata came from. - userAgent (optional): UA used for the requests to hCaptcha. Default: current Windows Chrome. Use a desktop Windows Chrome UA — the fingerprint is tuned for it. Submit the token under the same UA. solution: { token /* h-captcha-response / g-recaptcha-response */, userAgent /* submit the token under this exact UA — mismatch → score 0 */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "HCaptchaTask", "websiteURL": "https://example.com/login", "websitePublicKey": "a5f74b19-9e45-40e0-b45d-47ff91b7a6c2", "proxy": "http://user:pass@host:port" } } ``` ### Cloudflare Turnstile task.type: "TurnstileTask" Returns cf-turnstile-response (~300s lifetime). Parameters: - websiteURL (required): Page hosting the Turnstile widget. - websitePublicKey (required): Turnstile site key (starts with 0x...). - proxy (required): http://user:pass@host:port. Required. - metadata.action (optional): Widget data-action (turnstile.render action). Pass it if the site sets one — the token is bound to it. Top-level action is accepted too. - metadata.cdata (optional): Widget data-cdata (turnstile.render cData), if the site sets one. solution: { token, userAgent } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "TurnstileTask", "websiteURL": "https://example.com/login", "websitePublicKey": "0x4AAAAAAA...", "proxy": "http://user:pass@host:port", "metadata": { "action": "login" } } } ``` ### Cloudflare Challenge (cf_clearance) task.type: "CloudflareTask" Returns the cf_clearance cookie value for the "Just a moment..." challenge page (~5-10 s). Bound to the proxy IP and userAgent: send later requests through the same sticky proxy with the returned userAgent. Parameters: - websiteURL (required): Page behind the Cloudflare challenge. - proxy (required): http://user:pass@host:port, sticky session. Required — cf_clearance binds to the proxy IP. - userAgent (optional): Your Chrome User-Agent (desktop Chrome on Windows only). The cookie is issued for it; otherwise use the returned userAgent. - html (optional): The 403 "Just a moment..." page you received from the site through the same proxy. Optional; needed for some sites. solution: { token /* cf_clearance cookie value */, userAgent, cookies /* all site cookies, name → value */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "CloudflareTask", "websiteURL": "https://example.com/", "proxy": "http://user:pass@host:port" } } ``` ### FunCaptcha Classification (image recognition) task.type: "FunCaptchaClassification" Image-recognition for FunCaptcha tiles. Send one wave image (or a batch) + the game variant, get the picked tile indexes back. Parameters: - image (optional): Wave image as base64 (with or without data: prefix). Use this OR images. - images (optional): Array of base64 images for batch recognition. - question (required): Game variant (e.g. 3d_rollball_animals, 3d_rollball_objects, fish, card, icon_connect). solution: { text /* comma-separated tile indexes, 0-based */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "FunCaptchaClassification", "image": "", "question": "3d_rollball_animals" } } ``` ### Image to Text (OCR) task.type: "ImageToTextTask" Reads text off a captcha image. Russian and English. Parameters: - image (required): Captcha image as base64 (JPEG or PNG), no data: prefix. solution: { text } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "ImageToTextTask", "image": "" } } ``` ### DataDome Interstitial task.type: "DataDomeInterstitialTask" Solves a DataDome captcha — both interstitial and slider. Works on any DataDome-protected site. Reply with token + userAgent + secChUa + secChUaPlatform together on subsequent requests. Parameters: - websiteURL (required): Captcha URL. - proxy (required): http://user:pass@host:port. Schemes: http, https. - userAgent (optional): Optional. Default: Chrome 149 / Win. solution: { token, userAgent, secChUa, secChUaPlatform } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "DataDomeInterstitialTask", "websiteURL": "https://geo.captcha-delivery.com/interstitial/?initialCid=...&hash=...&cid=...&t=it", "proxy": "http://user:pass@host:port" } } ``` ### DataDome Preflight task.type: "DataDomePreflightTask" Mints a DataDome token in advance, before any captcha is shown — no browser, ~1-2s. Ideal flow: call this first, send your request with the token, and only if a 403 still arrives, fall back to DataDomeInterstitialTask on the URL from the 403. A request without a preflight token is highly likely to get a captcha the solver will not be able to crack. Supports ubisoft.com (login/email-change/reset-password forms — use preset), soundcloud.com (login/registration) and any other DataDome-protected page reached by plain URL (e.g. immobiliare.it) — the sidecar picks the right flow from refererURL's domain. Parameters: - refererURL (required): Page the token is for. For Ubisoft forms, the exact form page (see preset below); for every other site, just the page URL. - proxy (required): http://user:pass@host:port. Schemes: http, https. - userAgent (optional): Optional. Default: Chrome 149 / Win. - preset (optional): Optional, Ubisoft only: "login" (default), "email_change" or "reset_password" — picks which Ubisoft form flow to mint for. Ignored for every other site (routed by refererURL's domain instead). solution: { token, userAgent, secChUa, secChUaPlatform } — same shape as DataDomeInterstitialTask Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "DataDomePreflightTask", "refererURL": "https://www.ubisoft.com/en-us/account/action/change-password?strStamp=...", "proxy": "http://user:pass@host:port", "preset": "reset_password" } } ``` ### Math Image (OCR + eval) task.type: "MathImageTask" Arithmetic-on-image captchas ("6 × 3 = ?"). solution.text is the answer NUMBER, not the recognised expression — submit it verbatim. Accepts +, -, ×/*/x/X, /,: as operators. Parameters: - image (required): Captcha image as base64 (JPEG or PNG), no data: prefix. Crop to the captcha area when possible. solution: { text /* the computed result, e.g. "18" for a 6×3 image */ } Example createTask body: ```json { "clientKey": "fsk_your_api_key_here", "task": { "type": "MathImageTask", "image": "" } } ``` ## Error codes ERROR_KEY_DOES_NOT_EXIST: Missing, invalid or revoked API key. ERROR_TASK_TYPE_REQUIRED: task.type field is required. ERROR_UNKNOWN_TASK_TYPE: Server does not recognise this task.type. ERROR_WEBSITE_URL_REQUIRED: websiteURL is required. ERROR_WEBSITE_PUBLIC_KEY_REQUIRED: websitePublicKey is required. ERROR_PROXY_REQUIRED: TurnstileTask / CloudflareTask / FunCaptchaTask / CastleTask / DataDomeInterstitialTask / DataDomePreflightTask require a proxy (proxy="http://user:pass@host:port"). ERROR_PROXY_FORMAT: Proxy URL could not be parsed: scheme must be http/https/socks5, host:port required. ERROR_PROXY_UNREACHABLE: Proxy did not answer on TCP connect (2s timeout). Task NOT dispatched, no charge. ERROR_NO_BALANCE: Insufficient balance for one solve of this captcha. ERROR_RATE_LIMIT: Per-user createTask/min limit exceeded. ERROR_NO_SOLVER_AVAILABLE: No worker instance is currently subscribed to this captcha type. ERROR_SOLVER_OVERLOADED: All workers for this type are busy and the queue is saturated (>500 pending). ERROR_TIMEOUT: Task did not complete within 90 seconds; the gateway abandoned it (no charge). ERROR_TASK_ID_REQUIRED: taskId is required. ERROR_NO_SUCH_TASK: taskId not found (or expired). ERROR_SERVICE_BUSY: Transient infra issue. Retry shortly. ERROR_CAPTCHA_UNSOLVABLE: Solver could not produce a token. Retry. ## Rate limits Per user (shared across all keys): 1200 createTask/min and 70 concurrent tasks by default. getTaskResult is not rate-limited. ## End-to-end example (Python) ```python import requests, time API = "https://api.funsolver.com" KEY = "fsk_your_api_key_here" create = requests.post(f"{API}/createTask", json={ "clientKey": KEY, "task": { "type": "RecaptchaV2Task", "websiteURL": "https://example.com/login", "websitePublicKey": "6Lc_aCMTAAAAA...", "proxy": "http://user:pass@residential-host:port" }, }).json() task_id = create["taskId"] while True: r = requests.post(f"{API}/getTaskResult", json={"clientKey": KEY, "taskId": task_id}).json() if r.get("status") == "ready": print(r["solution"]["token"]); break if r.get("errorId"): raise RuntimeError(r["errorCode"], r.get("errorDescription")) time.sleep(2) ``` ## End-to-end example (cURL) ```bash curl -s https://api.funsolver.com/createTask -H 'Content-Type: application/json' -d '{ "clientKey": "fsk_your_api_key_here", "task": { "type": "ImageToTextTask", "image": "" } }' # -> {"errorId":0,"taskId":"..."} curl -s https://api.funsolver.com/getTaskResult -H 'Content-Type: application/json' -d '{ "clientKey": "fsk_your_api_key_here", "taskId": "" }' ```