Tutorials

Batch CAPTCHA Solving Cost Estimation and Budget Alerts

A batch of 10,000 reCAPTCHA v2 tasks costs differently than 10,000 image CAPTCHAs. Without cost tracking, a misconfigured loop can drain your balance before you notice. This guide shows how to estimate costs before a batch starts, track spending during execution, and set alerts when budgets are exceeded.

Cost Per CAPTCHA Type

Pricing varies by CAPTCHA complexity. Use these approximate rates for planning (check CaptchaAI pricing for current rates):

CAPTCHA Type Approximate Cost Notes
Image/Text OCR ~$0.001–0.003 Cheapest; high volume
reCAPTCHA v2 ~$0.002–0.004 Most common
reCAPTCHA v3 ~$0.003–0.005 Score-based
reCAPTCHA Enterprise ~$0.005–0.008 Higher complexity
hCaptcha ~$0.003–0.005 Similar to reCAPTCHA v2
Cloudflare Turnstile ~$0.003–0.005 Token-based
GeeTest v3 ~$0.004–0.006 Multi-step challenge

Python: Cost Estimator and Budget Guard

import requests
import time
from dataclasses import dataclass

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"

# Cost per 1,000 tasks (adjust to current pricing)
COST_PER_1K = {
    "userrecaptcha": 3.00,      # reCAPTCHA v2
    "userrecaptcha_v3": 4.00,   # reCAPTCHA v3
    "turnstile": 3.50,          # Cloudflare Turnstile
    "hcaptcha": 3.50,           # hCaptcha
    "base64": 1.50,             # Image/OCR
    "geetest": 5.00,            # GeeTest
}


@dataclass
class CostEstimate:
    task_count: int
    method: str
    cost_per_task: float
    total_estimated: float
    retry_buffer: float  # Extra for retries
    total_with_buffer: float

    def __str__(self):
        return (
            f"Estimate: {self.task_count} × ${self.cost_per_task:.4f} "
            f"= ${self.total_estimated:.2f} "
            f"(+{self.retry_buffer:.0%} retry buffer = ${self.total_with_buffer:.2f})"
        )


def estimate_cost(task_count, method="userrecaptcha", retry_rate=0.15):
    """Estimate total cost for a batch before starting."""
    per_1k = COST_PER_1K.get(method, 3.00)
    per_task = per_1k / 1000
    base_cost = task_count * per_task
    buffer_cost = base_cost * (1 + retry_rate)

    return CostEstimate(
        task_count=task_count,
        method=method,
        cost_per_task=per_task,
        total_estimated=base_cost,
        retry_buffer=retry_rate,
        total_with_buffer=buffer_cost,
    )


def get_balance():
    """Check current CaptchaAI balance."""
    resp = requests.get(RESULT_URL, params={
        "key": API_KEY, "action": "getbalance", "json": 1,
    }, timeout=10).json()
    return float(resp.get("request", 0))


class BudgetGuard:
    """Track spending and enforce budget limits during batch processing."""

    def __init__(self, budget_limit, method="userrecaptcha", alert_threshold=0.8):
        self.budget_limit = budget_limit
        self.alert_threshold = alert_threshold
        self.per_task = COST_PER_1K.get(method, 3.00) / 1000
        self.tasks_submitted = 0
        self.tasks_solved = 0
        self.tasks_failed = 0
        self.estimated_spent = 0.0
        self.alert_sent = False

    def can_submit(self):
        """Check if budget allows another task."""
        projected = self.estimated_spent + self.per_task
        if projected > self.budget_limit:
            print(f"BUDGET EXCEEDED: ${self.estimated_spent:.2f} / ${self.budget_limit:.2f}")
            return False
        return True

    def record_submit(self):
        """Record a submitted task."""
        self.tasks_submitted += 1
        self.estimated_spent += self.per_task

        # Alert at threshold
        if not self.alert_sent and self.estimated_spent >= self.budget_limit * self.alert_threshold:
            pct = (self.estimated_spent / self.budget_limit) * 100
            print(f"BUDGET ALERT: {pct:.0f}% used (${self.estimated_spent:.2f} / ${self.budget_limit:.2f})")
            self.alert_sent = True

    def record_result(self, solved):
        """Record a task result."""
        if solved:
            self.tasks_solved += 1
        else:
            self.tasks_failed += 1

    def summary(self):
        """Return a spending summary."""
        return {
            "submitted": self.tasks_submitted,
            "solved": self.tasks_solved,
            "failed": self.tasks_failed,
            "estimated_spent": round(self.estimated_spent, 4),
            "budget_limit": self.budget_limit,
            "budget_remaining": round(self.budget_limit - self.estimated_spent, 4),
            "cost_per_solved": round(
                self.estimated_spent / self.tasks_solved, 4
            ) if self.tasks_solved else 0,
        }


def solve_with_budget(tasks, budget_limit, method="userrecaptcha"):
    """Process tasks with budget enforcement."""
    # Pre-flight checks
    estimate = estimate_cost(len(tasks), method)
    print(estimate)

    balance = get_balance()
    print(f"Current balance: ${balance:.2f}")

    if estimate.total_with_buffer > balance:
        print(f"WARNING: Estimated cost ${estimate.total_with_buffer:.2f} exceeds balance ${balance:.2f}")
        return []

    effective_budget = min(budget_limit, balance)
    guard = BudgetGuard(effective_budget, method)

    results = []
    for i, task in enumerate(tasks):
        if not guard.can_submit():
            print(f"Budget limit reached after {i} tasks")
            break

        guard.record_submit()

        try:
            params = {"key": API_KEY, "method": method, "json": 1}
            if method == "userrecaptcha":
                params["googlekey"] = task["sitekey"]
                params["pageurl"] = task["pageurl"]
            elif method == "turnstile":
                params["sitekey"] = task["sitekey"]
                params["pageurl"] = task["pageurl"]

            resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
            if resp.get("status") != 1:
                guard.record_result(False)
                results.append({"index": i, "status": "failed", "error": resp.get("request")})
                continue

            task_id = resp["request"]
            solved = False
            for _ in range(60):
                time.sleep(5)
                poll = requests.get(RESULT_URL, params={
                    "key": API_KEY, "action": "get",
                    "id": task_id, "json": 1,
                }, timeout=15).json()

                if poll.get("request") == "CAPCHA_NOT_READY":
                    continue
                if poll.get("status") == 1:
                    guard.record_result(True)
                    results.append({"index": i, "status": "solved", "token": poll["request"]})
                    solved = True
                    break

                guard.record_result(False)
                results.append({"index": i, "status": "failed", "error": poll.get("request")})
                break

            if not solved and results[-1]["index"] != i:
                guard.record_result(False)
                results.append({"index": i, "status": "failed", "error": "timeout"})

        except Exception as e:
            guard.record_result(False)
            results.append({"index": i, "status": "failed", "error": str(e)})

    print(f"\nSpending summary: {guard.summary()}")
    return results


# Usage
tasks = [
    {"sitekey": "SITE_KEY", "pageurl": f"https://example.com/page{i}"}
    for i in range(100)
]

# Estimate before running
est = estimate_cost(100, "userrecaptcha")
print(est)
# Estimate: 100 × $0.0030 = $0.30 (+15% retry buffer = $0.35)

# Run with $0.50 budget limit
results = solve_with_budget(tasks, budget_limit=0.50, method="userrecaptcha")

JavaScript: Cost Tracker

const API_KEY = "YOUR_API_KEY";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

const COST_PER_1K = {
  userrecaptcha: 3.0,
  turnstile: 3.5,
  hcaptcha: 3.5,
  base64: 1.5,
};

function estimateCost(taskCount, method = "userrecaptcha", retryRate = 0.15) {
  const perTask = (COST_PER_1K[method] || 3.0) / 1000;
  const base = taskCount * perTask;
  return {
    taskCount,
    perTask: perTask.toFixed(4),
    baseCost: base.toFixed(2),
    withRetries: (base * (1 + retryRate)).toFixed(2),
  };
}

class BudgetTracker {
  constructor(limit, method = "userrecaptcha", alertPct = 0.8) {
    this.limit = limit;
    this.perTask = (COST_PER_1K[method] || 3.0) / 1000;
    this.spent = 0;
    this.alertPct = alertPct;
    this.alertFired = false;
    this.solved = 0;
    this.failed = 0;
  }

  canSubmit() {
    return this.spent + this.perTask <= this.limit;
  }

  recordSubmit() {
    this.spent += this.perTask;
    if (!this.alertFired && this.spent >= this.limit * this.alertPct) {
      console.warn(`BUDGET ALERT: ${((this.spent / this.limit) * 100).toFixed(0)}% used`);
      this.alertFired = true;
    }
  }

  recordResult(success) {
    if (success) this.solved++;
    else this.failed++;
  }

  summary() {
    return {
      spent: `$${this.spent.toFixed(4)}`,
      remaining: `$${(this.limit - this.spent).toFixed(4)}`,
      solved: this.solved,
      failed: this.failed,
      costPerSolved: this.solved ? `$${(this.spent / this.solved).toFixed(4)}` : "N/A",
    };
  }
}

async function checkBalance() {
  const url = `${RESULT_URL}?key=${API_KEY}&action=getbalance&json=1`;
  const resp = await (await fetch(url)).json();
  return parseFloat(resp.request);
}

// Usage
const estimate = estimateCost(500, "userrecaptcha");
console.log("Estimate:", estimate);

const tracker = new BudgetTracker(2.0, "userrecaptcha");
// Use tracker.canSubmit() / tracker.recordSubmit() / tracker.recordResult() during batch processing

Budget Planning Table

Batch Size reCAPTCHA v2 Image/OCR Turnstile With 15% Retry Buffer
100 $0.30 $0.15 $0.35 $0.35–0.40
1,000 $3.00 $1.50 $3.50 $3.45–4.03
10,000 $30.00 $15.00 $35.00 $34.50–40.25
100,000 $300.00 $150.00 $350.00 $345–402

Troubleshooting

Issue Cause Fix
Cost higher than estimated Retry rate exceeds buffer Increase retry buffer to 25–30% for challenging CAPTCHAs
Balance drained unexpectedly No budget guard in place Add BudgetGuard with hard limit before production runs
Cost per solved task too high Many failures not counted Track both submitted and solved counts — optimize failure rate first
Estimate doesn't match actual Using wrong cost-per-1K rate Check current CaptchaAI pricing page and update COST_PER_1K
Alert fires too late Threshold set too high Lower alert_threshold to 0.5 (50%) for tighter budgets

FAQ

How accurate are cost estimates?

Within 10–20% for typical batches. The main variable is retry rate — challenging sites or bad proxies increase retries. Run a small test batch (50–100 tasks) to measure your actual retry rate before committing to large batches.

Should I check balance before every task?

No — balance API calls add latency. Track spending locally with a BudgetGuard and check actual balance every 100–500 tasks or at the start/end of each batch.

How do I set up spending alerts for a team?

Run a scheduled balance check (every hour) that compares current balance to previous. If the delta exceeds a threshold (e.g., $5/hour), trigger a notification via Slack webhook, email, or PagerDuty.

Next Steps

Keep your CAPTCHA solving costs predictable — get your CaptchaAI API key and implement budget tracking from the start.

Related guides:

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.