API Tutorials

How to Solve Friendly Captcha Using API

Takeaway up front: to solve Friendly Captcha with CaptchaAI, read the data-sitekey off the frc-captcha element, submit it with method=friendly_captcha to https://ocr.captchaai.com/in.php, poll res.php for a single token, then inject that token into the frc-captcha-solution field before you submit the form. Friendly Captcha support is currently in beta, so this guide sets realistic expectations along the way.

This is the hands-on, copy-paste tutorial. If you want the mental model first — what proof-of-work verification is, why Friendly Captcha behaves the way it does, and how to reason about the moving parts — read the companion explainer, How Friendly Captcha Works and How to Handle It. This article does not re-explain the mechanism; it walks the exact API calls.


Beta status: what to expect

Friendly Captcha is a beta solver method on CaptchaAI. That means the endpoint, the friendly_captcha method string, and the request/response shape are stable enough to build against, but you should treat the integration as one you monitor rather than fire-and-forget:

  • Build a retry budget and a hard timeout into the client from day one (both are shown below).
  • Do not hardcode assumptions about a fixed solve time — beta methods have no published solve-speed or success-rate figure, so measure your own p50/p95 in your environment.
  • Keep the same submit/poll/inject flow you already use for other CaptchaAI types; only the method value changes.

Everywhere this guide asserts that CaptchaAI solves Friendly Captcha, read it as "supported in beta."


Requirements

Item Value
CaptchaAI API key From captchaai.com
Friendly Captcha sitekey Read from the frc-captcha element on the page
Page URL The full URL where the widget appears
Language Python 3.7+ or Node.js 14+
Optional proxy proxy + proxytype if the site is geofenced

Step 1: Extract the sitekey

Friendly Captcha renders as a widget with the class frc-captcha. The value you need is the data-sitekey attribute on that element.

<div class="frc-captcha" data-sitekey="FCMGEMUD2M567T8G" data-start="auto"></div>

To find it:

  1. Open DevTools → Elements tab.
  2. Search (Ctrl+F) for frc-captcha.
  3. Copy the data-sitekey value.

Read the sitekey from the live DOM rather than a cached copy of the page source — some integrations inject the widget after load, and a stale value produces ERROR_WRONG_SITEKEY.


Step 2: Submit the task

Send a GET request to https://ocr.captchaai.com/in.php with the page URL and sitekey. The only required parameters are key, method, pageurl, and sitekey. Add proxy and proxytype only if the target site is region-locked.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

# Submit task
response = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": API_KEY,
    "method": "friendly_captcha",
    "sitekey": "FCMGEMUD2M567T8G",
    "pageurl": "https://example.com/login",
    "json": 1
})

data = response.json()
if data.get("status") != 1:
    raise Exception(f"Submit error: {data.get('request')}")

task_id = data["request"]
print(f"Task submitted: {task_id}")

Node.js

const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';

async function submitFriendly(sitekey, pageurl) {
  const { data } = await axios.get('https://ocr.captchaai.com/in.php', {
    params: {
      key: API_KEY,
      method: 'friendly_captcha',
      sitekey: sitekey,
      pageurl: pageurl,
      json: 1
    }
  });

  if (data.status !== 1) throw new Error(`Submit error: ${data.request}`);
  return data.request;
}

A successful submit returns {"status": 1, "request": "<task_id>"}. Any other status means the request never entered the queue — surface the request string as your error message and stop.


Step 3: Poll for the token (polling discipline)

The token is not ready instantly. Poll https://ocr.captchaai.com/res.php with action=get and your task_id. The discipline that keeps this cheap and reliable:

  • Wait before the first poll. Give the solver a head start — wait ~15 seconds before polling instead of hammering res.php immediately.
  • Poll on a fixed interval. Every 5 seconds is plenty. Faster adds load and noise without getting the answer sooner.
  • Cap the total wait. Stop after a hard ceiling (120 seconds below) and treat it as a timeout rather than looping forever.
  • Only CAPCHA_NOT_READY means "keep waiting." Any other non-solved request value is a terminal error — raise it immediately instead of retrying.

For a deeper comparison of polling versus a callback-based flow, see Callback vs Polling.

Python

def get_solution(task_id):
    time.sleep(15)  # give the solver a head start
    for attempt in range(21):  # ~120s ceiling at 5s intervals
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1
        }).json()

        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise Exception(f"Error: {result.get('request')}")
        time.sleep(5)

    raise Exception("Timeout: solution not received")

token = get_solution(task_id)
print(f"Token: {token[:40]}...")

Node.js

async function getSolution(taskId) {
  await new Promise(r => setTimeout(r, 15000));  // head start
  for (let i = 0; i < 21; i++) {                 // ~120s ceiling
    const { data } = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
    });

    if (data.status === 1) return data.request;
    if (data.request !== 'CAPCHA_NOT_READY') throw new Error(data.request);
    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error('Timeout: solution not received');
}

The solved response is a single token string in the request field, for example 72b43902cd1457f6880dd3735c50070d.Z0+9+Hcx7KGSmHd1AQwtjQ.... There is no separate validate or seccode — Friendly Captcha returns one value.


Step 4: Inject the token into the form

Friendly Captcha expects the token in a hidden input named frc-captcha-solution (some sites override this with a data-solution-field-name attribute). Inject the value and dispatch input/change events so any listeners on the form pick it up, then submit as normal.

Run this in the browser context that rendered the widget — for example via Selenium or Playwright evaluate — passing the token you retrieved:

(token) => {
  const widget = document.querySelector('.frc-captcha');
  const fieldName = widget?.getAttribute('data-solution-field-name')
    || 'frc-captcha-solution';
  const form = widget?.closest('form') || document.querySelector('form') || document.body;

  let input = form.querySelector(`input[name="${fieldName}"]`);
  if (!input) {
    input = document.createElement('input');
    input.type = 'hidden';
    input.name = fieldName;
    form.appendChild(input);
  }

  input.value = token;
  input.dispatchEvent(new Event('input', { bubbles: true }));
  input.dispatchEvent(new Event('change', { bubbles: true }));

  return { ok: true, fieldName };
}

If your workflow posts the form server-side instead of through a browser, send the token as the frc-captcha-solution form field in the same request that carries the rest of your form data. For a broader reference on injection patterns across CAPTCHA types, see CAPTCHA Token Injection Methods.


Complete Python example

import requests
import time

API_KEY = "YOUR_API_KEY"
SITEKEY = "FCMGEMUD2M567T8G"
PAGE_URL = "https://example.com/login"

# 1. Submit
resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": API_KEY, "method": "friendly_captcha",
    "sitekey": SITEKEY, "pageurl": PAGE_URL, "json": 1
}).json()
if resp.get("status") != 1:
    raise SystemExit(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]

# 2. Poll (15s head start, then every 5s, 120s ceiling)
token = None
time.sleep(15)
for _ in range(21):
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": 1
    }).json()
    if result.get("status") == 1:
        token = result["request"]
        break
    if result.get("request") != "CAPCHA_NOT_READY":
        raise SystemExit(f"Solve error: {result.get('request')}")
    time.sleep(5)

if not token:
    raise SystemExit("Timeout: no token returned")

# 3. Submit the form with the token injected as frc-captcha-solution
submit = requests.post(PAGE_URL, data={
    "frc-captcha-solution": token,
    "email": "user@example.com"
})
print(f"Result: {submit.status_code}")

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Key malformed or copied with whitespace Re-copy the 32-character key from the dashboard
ERROR_KEY_DOES_NOT_EXIST Wrong or rotated key Confirm the active key and update your secret
ERROR_ZERO_BALANCE Balance too low to queue the task Top up and add a balance alert
ERROR_PAGEURL pageurl missing Send the full page URL, including query string
ERROR_WRONG_SITEKEY Sitekey stale or malformed Re-read data-sitekey from the live DOM
ERROR_BAD_PROXY Proxy unreachable Verify proxy and proxytype, or drop them
CAPCHA_NOT_READY Token not solved yet Keep polling — this is expected, not an error
Token rejected by site Wrong session, or field name overridden Submit in the same session; honor data-solution-field-name

For the full error catalog, see the CaptchaAI Error Handling guide.


Full runnable example

Need a complete project with environment setup, retries, and error handling wired together?

See the full runnable example on GitHub →


FAQ

Is Friendly Captcha fully supported by CaptchaAI?

It is supported in beta. The friendly_captcha method works and follows the same submit/poll/inject flow as other CaptchaAI types, but because it is beta there are no published solve-speed or success-rate figures — measure your own latency in your environment and keep retries and timeouts in place.

What field does the token go into?

The default is a hidden input named frc-captcha-solution. Some sites rename it via a data-solution-field-name attribute on the frc-captcha element, so read that attribute and fall back to frc-captcha-solution if it is absent.

How is this different from the Friendly Captcha explainer article?

The explainer covers the concepts — the proof-of-work model, KPIs, and how to reason about the integration. This tutorial is the hands-on counterpart: the exact API parameters, the polling loop, and the injection code. Read the explainer for the "why," use this for the "how."

Do I need a proxy to solve Friendly Captcha?

Usually not. The required parameters are just key, method, pageurl, and sitekey. Add proxy and proxytype only when the target site is geofenced or you need the solve to originate from a specific region.

Why wait 15 seconds before the first poll?

The token takes time to compute, so an immediate poll almost always returns CAPCHA_NOT_READY. Waiting ~15 seconds before polling, then checking every 5 seconds up to a 120-second ceiling, gets you the answer without wasting requests on res.php.



Start solving CAPTCHAs with CaptchaAI

Grab your API key, drop in the friendly_captcha method, and run the complete example above against your own form.

Start solving CAPTCHAs with CaptchaAI

Comments are disabled for this article.