Getting Started

CaptchaAI Quickstart: Your First Solve in 5 Minutes

This guide gets you from zero to a solved CAPTCHA as fast as possible. No theory, no deep dives — just the minimum steps to make your first successful API call and get a solved token back.

Every CAPTCHA type CaptchaAI supports follows the same four-step pattern:

  1. Submit — send the CAPTCHA details to in.php
  2. Get the task ID — save the ID from the response
  3. Poll — check res.php every 5 seconds until the result is ready
  4. Use the token — inject the solved token into the target page or request
sequenceDiagram
    autonumber
    participant App as Your client
    participant In as ocr.captchaai.com/in.php
    participant Res as ocr.captchaai.com/res.php

    App->>In: POST key + method + sitekey + pageurl
    In-->>App: {"status": 1, "request": "<task_id>"}
    Note over App: wait ~15s before first poll
    loop every 5s until status=1
        App->>Res: GET key + action=get + id=<task_id>
        alt still solving
            Res-->>App: CAPCHA_NOT_READY
        else solved
            Res-->>App: {"status": 1, "request": "<token>"}
        else error
            Res-->>App: ERROR_*
        end
    end
    App->>App: inject token into target form / request

Step 0: Get your API key

  1. Sign up at captchaai.com
  2. Go to your dashboard
  3. Copy your 32-character API key

You need active threads on your account to submit tasks. Contact support for a free trial if you are evaluating the service.


Step 1: Submit a CAPTCHA

This example solves a Cloudflare Turnstile challenge — one of the most common types. You need two inputs from the target page:

  • sitekey — the public key tied to the Turnstile widget (find it in the data-sitekey attribute or the Turnstile script parameters, starts with 0x)
  • pageurl — the full URL where the widget loads

cURL

curl -X POST "https://ocr.captchaai.com/in.php" \
  -d "key=YOUR_API_KEY" \
  -d "method=turnstile" \
  -d "sitekey=0x4AAAAAAAC3DHQFLr1GavNl" \
  -d "pageurl=https://example.com/login" \
  -d "json=1"

Python

import requests

response = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": "YOUR_API_KEY",
    "method": "turnstile",
    "sitekey": "0x4AAAAAAAC3DHQFLr1GavNl",
    "pageurl": "https://example.com/login",
    "json": 1,
})
print(response.json())

Node.js

const response = await fetch("https://ocr.captchaai.com/in.php", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    key: "YOUR_API_KEY",
    method: "turnstile",
    sitekey: "0x4AAAAAAAC3DHQFLr1GavNl",
    pageurl: "https://example.com/login",
    json: "1",
  }),
});
console.log(await response.json());

PHP

<?php
$response = file_get_contents("https://ocr.captchaai.com/in.php?" . http_build_query([
    "key"       => "YOUR_API_KEY",
    "method"    => "turnstile",
    "sitekey"   => "0x4AAAAAAAC3DHQFLr1GavNl",
    "pageurl"   => "https://example.com/login",
    "json"      => 1,
]));
echo $response;

Step 2: Save the task ID

You will receive:

{
  "status": 1,
  "request": "71823469"
}

The request field is your task ID. Save it — you need it to get the result.

If status is 0, something went wrong. Check the request field for the error code:

Error Meaning Fix
ERROR_WRONG_USER_KEY API key format is wrong Check your 32-character key
ERROR_KEY_DOES_NOT_EXIST API key not found Verify the key from your dashboard
ERROR_ZERO_BALANCE No available threads Top up or wait for threads to free
ERROR_PAGEURL Missing pageurl parameter Add the full page URL
ERROR_WRONG_GOOGLEKEY Sitekey is blank or malformed Re-extract the sitekey from the page (starts with 0x for Turnstile)

Step 3: Poll for the result

Wait 15 seconds, then check every 5 seconds until the result is ready.

cURL

curl "https://ocr.captchaai.com/res.php?key=YOUR_API_KEY&action=get&id=71823469&json=1"

Python

import time

time.sleep(15)

while True:
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": "71823469",
        "json": 1,
    }).json()

    if result["request"] == "CAPCHA_NOT_READY":
        time.sleep(5)
        continue

    if result["status"] == 1:
        token = result["request"]
        print(f"Solved! Token: {token[:60]}...")
        break

    print(f"Error: {result}")
    break

Node.js

await new Promise((r) => setTimeout(r, 15_000));

while (true) {
  const result = await fetch(
    `https://ocr.captchaai.com/res.php?${new URLSearchParams({
      key: "YOUR_API_KEY",
      action: "get",
      id: "71823469",
      json: "1",
    })}`
  ).then((r) => r.json());

  if (result.request === "CAPCHA_NOT_READY") {
    await new Promise((r) => setTimeout(r, 5_000));
    continue;
  }

  if (result.status === 1) {
    console.log(`Solved! Token: ${result.request.slice(0, 60)}...`);
    break;
  }

  console.error("Error:", result);
  break;
}

PHP

<?php
sleep(15);

while (true) {
    $result = json_decode(file_get_contents("https://ocr.captchaai.com/res.php?" . http_build_query([
        "key"    => "YOUR_API_KEY",
        "action" => "get",
        "id"     => "71823469",
        "json"   => 1,
    ])), true);

    if ($result["request"] === "CAPCHA_NOT_READY") {
        sleep(5);
        continue;
    }

    if ($result["status"] === 1) {
        echo "Solved! Token: " . substr($result["request"], 0, 60) . "...\n";
        break;
    }

    echo "Error: " . json_encode($result) . "\n";
    break;
}

When the solve completes, you get:

{
  "status": 1,
  "request": "03AHJ_Vuve5Asa4koK3KSMyUkCq0vUFCR5Im4CwB7PzO3dCxIo..."
}

The request field is the solved CAPTCHA token.


Step 4: Use the token

For Cloudflare Turnstile, inject the token into the cf-turnstile-response field on the target page:

With Selenium (Python):

driver.execute_script(
    'document.querySelector("[name=cf-turnstile-response]").value = arguments[0];',
    token
)
driver.find_element("css selector", "form").submit()

With Puppeteer (Node.js):

await page.evaluate((token) => {
  document.querySelector("[name=cf-turnstile-response]").value = token;
}, token);
await page.click("#submit-button");

Without a browser (HTTP request):

response = requests.post("https://example.com/login", data={
    "username": "user@example.com",
    "password": "your_password",
    "cf-turnstile-response": token,
})

End-to-end script

Here's everything above stitched into a single runnable Python script. Replace the three placeholders and run it; it prints the solved token on success or the precise error on failure.

import os
import time
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
SITE_KEY = "0x4AAAAAAAC3DHQFLr1GavNl"
PAGE_URL = "https://example.com/login"

BASE = "https://ocr.captchaai.com"


def solve_turnstile() -> str:
    # 1. Submit
    submit = requests.post(f"{BASE}/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": SITE_KEY,
        "pageurl": PAGE_URL,
        "json": 1,
    }, timeout=30).json()
    if submit.get("status") != 1:
        raise RuntimeError(f"submit failed: {submit.get('request')}")
    task_id = submit["request"]

    # 2. Wait before the first poll
    time.sleep(15)

    # 3. Poll until ready or timeout
    deadline = time.time() + 180
    while time.time() < deadline:
        result = requests.get(f"{BASE}/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }, timeout=15).json()
        if result["request"] == "CAPCHA_NOT_READY":
            time.sleep(5)
            continue
        if result.get("status") == 1:
            return result["request"]
        raise RuntimeError(f"solve failed: {result.get('request')}")

    raise TimeoutError(f"task {task_id} did not finish in time")


if __name__ == "__main__":
    token = solve_turnstile()
    print(f"Solved! Token: {token[:60]}...")

Expected runtime: 15–30 seconds for a typical Turnstile solve. If you see CAPCHA_NOT_READY for more than 60 seconds in a row, the task is likely stuck — cancel and resubmit.


Common first-call mistakes

These trip up almost everyone on day one. Check each before opening a support ticket:

  • Polling too early. The first poll must wait ~15 seconds for token-based CAPTCHAs (Turnstile, reCAPTCHA, hCaptcha). Polling at t=0 produces CAPCHA_NOT_READY for every check and wastes one of your concurrent slots.
  • Wrong sitekey for the page. Sitekeys are page-bound. Copying a Turnstile sitekey from one site and using it against another returns ERROR_CAPTCHA_UNSOLVABLE or yields a token the target rejects with HTTP 403.
  • Missing json=1 then parsing as JSON. Without json=1 the API returns plain text like OK|71823469. Calling .json() on that throws a parse error.
  • Re-using a solved token. Turnstile and reCAPTCHA tokens are single-use and expire in ~120 seconds. Submit → use → discard. Never cache.
  • Hard-coding the API key. Use CAPTCHAAI_API_KEY from the environment so the key never lands in a screenshot or a git commit. See API Key Setup and Authentication for the full pattern.

What to try next

  • Different CAPTCHA types — swap method=turnstile for userrecaptcha, hcaptcha, geetest, funcaptcha, or base64 (image OCR). The submit/poll/use loop stays the same; only the parameters and the token field on the target page change.
  • Stop hard-coding values — move the API key and sitekey into config and read them at startup. The Production Configuration Management guide shows a clean layered loader.
  • Make failures recoverable — wrap submit and poll in the retry logic pattern so transient ERROR_NO_SLOT_AVAILABLE and HTTP 429 responses self-heal.

Part of the API Quickstart to Production series — eight practical guides covering CaptchaAI's API end-to-end, from your first request to running it reliably in production:

  1. CaptchaAI Quickstart: Your First Solve in 5 Minutes — you are here
  2. API Key Setup and Authentication
  3. API Response Formats Explained
  4. Error Codes: Complete Reference and Fixes
  5. Error Handling: Complete Decision Tree
  6. Implementing Robust Retry Logic
  7. Rate Limiting: Handling 429 Responses
  8. Production Configuration Management
Comments are disabled for this article.