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:
- Submit — send the CAPTCHA details to
in.php - Get the task ID — save the ID from the response
- Poll — check
res.phpevery 5 seconds until the result is ready - 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
- Sign up at captchaai.com
- Go to your dashboard
- 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-sitekeyattribute or the Turnstile script parameters, starts with0x) - 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_READYfor 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_UNSOLVABLEor yields a token the target rejects with HTTP 403. - Missing
json=1then parsing as JSON. Withoutjson=1the API returns plain text likeOK|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_KEYfrom 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=turnstileforuserrecaptcha,hcaptcha,geetest,funcaptcha, orbase64(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_AVAILABLEand HTTP 429 responses self-heal.
Related articles in this series
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:
- CaptchaAI Quickstart: Your First Solve in 5 Minutes — you are here
- API Key Setup and Authentication
- API Response Formats Explained
- Error Codes: Complete Reference and Fixes
- Error Handling: Complete Decision Tree
- Implementing Robust Retry Logic
- Rate Limiting: Handling 429 Responses
- Production Configuration Management