API Tutorials

Custom Timeout Settings for Different CAPTCHA Types

Each CAPTCHA type has different solving times. Using a single timeout for all types either wastes time on fast CAPTCHAs or times out prematurely on slower ones. Configure per-type timeouts for optimal performance.


CAPTCHA Type Avg Solve Time Initial Wait Poll Interval Max Timeout
Image/OCR 2-5s 3s 3s 30s
reCAPTCHA v2 10-20s 10s 5s 90s
reCAPTCHA v3 5-15s 5s 5s 60s
reCAPTCHA Enterprise 10-25s 10s 5s 120s
Invisible reCAPTCHA 10-20s 10s 5s 90s
Turnstile 3-10s 3s 3s 45s
Cloudflare Challenge 10-30s 10s 5s 120s
GeeTest v3 5-15s 5s 5s 60s
BLS 3-10s 3s 5s 45s

Type-Aware Solver

import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://ocr.captchaai.com"

# Per-type timeout configuration
TIMEOUT_CONFIG = {
    "base64": {
        "initial_wait": 3,
        "poll_interval": 3,
        "max_timeout": 30,
    },
    "userrecaptcha": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 90,
    },
    "userrecaptcha_v3": {
        "initial_wait": 5,
        "poll_interval": 5,
        "max_timeout": 60,
    },
    "turnstile": {
        "initial_wait": 3,
        "poll_interval": 3,
        "max_timeout": 45,
    },
    "cloudflare_challenge": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 120,
    },
    "geetest": {
        "initial_wait": 5,
        "poll_interval": 5,
        "max_timeout": 60,
    },
    "bls": {
        "initial_wait": 3,
        "poll_interval": 5,
        "max_timeout": 45,
    },
    "default": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 120,
    },
}


def get_config_key(method, **params):
    """Determine config key from method and parameters."""
    if method == "userrecaptcha" and params.get("version") == "v3":
        return "userrecaptcha_v3"
    return method


def solve(method, **params):
    """Solve CAPTCHA with type-appropriate timeouts."""
    config_key = get_config_key(method, **params)
    config = TIMEOUT_CONFIG.get(config_key, TIMEOUT_CONFIG["default"])

    # Submit task
    data = {"key": API_KEY, "method": method, "json": 1}
    data.update(params)
    resp = requests.post(f"{BASE_URL}/in.php", data=data, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Wait before first poll
    time.sleep(config["initial_wait"])

    # Poll with type-specific interval and timeout
    start = time.time()
    while time.time() - start < config["max_timeout"]:
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()

        if data["request"] != "CAPCHA_NOT_READY":
            elapsed = time.time() - start + config["initial_wait"]
            print(f"Solved {method} in {elapsed:.1f}s")
            return data["request"]

        time.sleep(config["poll_interval"])

    raise TimeoutError(
        f"{method} timeout after {config['max_timeout']}s"
    )


# Usage — each type uses optimal timeouts automatically
# Image (fast: 3s wait, 3s poll, 30s max)
token = solve("base64", body=base64_image)

# reCAPTCHA v2 (medium: 10s wait, 5s poll, 90s max)
token = solve("userrecaptcha", googlekey="KEY", pageurl="https://example.com")

# Turnstile (fast: 3s wait, 3s poll, 45s max)
token = solve("turnstile", sitekey="KEY", pageurl="https://example.com")

Dynamic Timeout Adjustment

Adjust timeouts based on observed solve times:

import statistics


class AdaptiveTimeoutSolver:
    """Adjusts timeouts based on historical solve times."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"
        self.history = {}  # method -> [solve_times]

    def solve(self, method, **params):
        config = self._get_config(method)

        # Submit
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)
        resp = requests.post(f"{self.base}/in.php", data=data, timeout=30)
        task_id = resp.json()["request"]

        time.sleep(config["initial_wait"])
        start = time.time()

        # Poll with adaptive timeout
        while time.time() - start < config["max_timeout"]:
            resp = requests.get(f"{self.base}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = resp.json()

            if data["request"] != "CAPCHA_NOT_READY":
                elapsed = time.time() - start + config["initial_wait"]
                self._record(method, elapsed)
                return data["request"]

            time.sleep(config["poll_interval"])

        raise TimeoutError(f"Timeout after {config['max_timeout']}s")

    def _get_config(self, method):
        """Get timeout config, adjusted by history."""
        base = TIMEOUT_CONFIG.get(method, TIMEOUT_CONFIG["default"])

        # If we have history, adjust max_timeout
        times = self.history.get(method, [])
        if len(times) >= 5:
            p95 = sorted(times)[int(len(times) * 0.95)]
            adjusted_timeout = max(p95 * 2, base["max_timeout"])
            return {**base, "max_timeout": adjusted_timeout}

        return base

    def _record(self, method, elapsed):
        if method not in self.history:
            self.history[method] = []
        self.history[method].append(elapsed)
        # Keep last 100 entries
        if len(self.history[method]) > 100:
            self.history[method] = self.history[method][-100:]

    def get_stats(self, method):
        times = self.history.get(method, [])
        if not times:
            return None
        return {
            "count": len(times),
            "mean": statistics.mean(times),
            "median": statistics.median(times),
            "p95": sorted(times)[int(len(times) * 0.95)],
            "max": max(times),
        }


# Usage
solver = AdaptiveTimeoutSolver("YOUR_API_KEY")
token = solver.solve("turnstile", sitekey="KEY", pageurl="https://example.com")
print(solver.get_stats("turnstile"))

Submit Timeout vs Poll Timeout

Two different timeouts to configure:

Submit timeout: How long to wait for the API to accept your task
  → Set to 30s (network issues only)

Poll timeout: How long to wait for the solve result
  → Varies by CAPTCHA type (30s to 120s)
# Submit timeout (fixed, short)
resp = requests.post(
    f"{BASE_URL}/in.php", data=data,
    timeout=30,  # 30s is plenty for submission
)

# Poll timeout (varies by type)
resp = requests.get(
    f"{BASE_URL}/res.php", params=params,
    timeout=15,  # 15s per individual poll request
)
# Overall polling loop timeout: 30-120s depending on type

Troubleshooting

Issue Cause Fix
Image CAPTCHAs timing out at 120s Timeout too long, wastes time Reduce to 30s for images
reCAPTCHA v2 timing out Max timeout too short Use 90s minimum for reCAPTCHA v2
First poll always returns "not ready" Initial wait too short Increase initial wait per type table
Excessive polling requests Poll interval too short Use 5s for token types, 3s for images

FAQ

Why not use the same timeout for everything?

A 120s timeout for image CAPTCHAs (which solve in 3s) wastes 117s on failures. A 30s timeout for reCAPTCHA Enterprise may timeout on valid solves. Per-type timeouts optimize both cases.

What happens if I set the timeout too low?

You'll get false timeout errors. The task may still complete on CaptchaAI's side, but you won't receive the result. Set timeouts at least 2x the average solve time.

Should I increase timeouts during peak hours?

Generally no. CaptchaAI's AI-based solving maintains consistent speeds. If you see increased solve times, check your network latency first.



Optimize every millisecond — try CaptchaAI with type-tuned timeouts.

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.