Building a SaaS platform that includes CAPTCHA solving requires per-tenant isolation, usage tracking, and cost allocation. Here's how to architect it.
Architecture Overview
┌──────────────────────────────────────┐
│ SaaS Platform │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Tenant A │ │ CAPTCHA Service │ │
│ │ Tenant B │──│ - Rate limiter │──│── CaptchaAI API
│ │ Tenant C │ │ - Usage tracker │ │
│ └──────────┘ │ - Cost allocator │ │
│ └──────────────────┘ │
└──────────────────────────────────────┘
Tenant-Isolated Solver
import threading
import time
import requests
from collections import defaultdict
class TenantCaptchaService:
"""CAPTCHA solving service with per-tenant isolation."""
def __init__(self, api_key):
self.api_key = api_key
self.usage = defaultdict(lambda: {"solves": 0, "cost": 0.0, "failures": 0})
self.limits = {}
self._lock = threading.Lock()
def configure_tenant(self, tenant_id, daily_limit=1000, rate_per_minute=10):
"""Set limits for a tenant."""
self.limits[tenant_id] = {
"daily_limit": daily_limit,
"rate_per_minute": rate_per_minute,
"minute_counter": 0,
"minute_start": time.time(),
}
def solve(self, tenant_id, captcha_type, params):
"""Solve CAPTCHA for a specific tenant."""
self._check_limits(tenant_id)
# Solve via CaptchaAI
data = {"key": self.api_key, "json": 1, **params}
resp = requests.post(
"https://ocr.captchaai.com/in.php", data=data, timeout=30,
)
result = resp.json()
if result.get("status") != 1:
with self._lock:
self.usage[tenant_id]["failures"] += 1
raise RuntimeError(result.get("request"))
task_id = result["request"]
# Poll
time.sleep(10)
for _ in range(24):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
self._record_usage(tenant_id, captcha_type)
return data["request"]
if data["request"] != "CAPCHA_NOT_READY":
with self._lock:
self.usage[tenant_id]["failures"] += 1
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
def _check_limits(self, tenant_id):
"""Enforce tenant rate and daily limits."""
with self._lock:
limits = self.limits.get(tenant_id)
if not limits:
return # No limits configured
# Daily limit
if self.usage[tenant_id]["solves"] >= limits["daily_limit"]:
raise TenantLimitExceeded(
f"Tenant {tenant_id} daily limit reached "
f"({limits['daily_limit']} solves)"
)
# Rate limit
now = time.time()
if now - limits["minute_start"] > 60:
limits["minute_counter"] = 0
limits["minute_start"] = now
if limits["minute_counter"] >= limits["rate_per_minute"]:
raise TenantRateLimited(
f"Tenant {tenant_id} rate limited "
f"({limits['rate_per_minute']}/min)"
)
limits["minute_counter"] += 1
def _record_usage(self, tenant_id, captcha_type):
"""Record successful solve for billing."""
cost_map = {
"recaptcha_v2": 0.003,
"recaptcha_v3": 0.004,
"turnstile": 0.002,
"image": 0.001,
}
cost = cost_map.get(captcha_type, 0.003)
with self._lock:
self.usage[tenant_id]["solves"] += 1
self.usage[tenant_id]["cost"] += cost
def get_tenant_usage(self, tenant_id):
"""Get usage stats for a tenant."""
with self._lock:
return dict(self.usage[tenant_id])
def get_all_usage(self):
"""Get usage for all tenants."""
with self._lock:
return {tid: dict(data) for tid, data in self.usage.items()}
class TenantLimitExceeded(Exception):
pass
class TenantRateLimited(Exception):
pass
Usage Tracking for Billing
import csv
import os
from datetime import datetime, timezone
class TenantBillingTracker:
"""Track per-tenant usage for billing purposes."""
def __init__(self, log_dir="billing_logs"):
os.makedirs(log_dir, exist_ok=True)
self.log_dir = log_dir
def record_solve(self, tenant_id, captcha_type, cost, task_id):
"""Record a billable solve."""
date = datetime.now(timezone.utc).strftime("%Y-%m")
filepath = os.path.join(self.log_dir, f"{tenant_id}_{date}.csv")
write_header = not os.path.exists(filepath)
with open(filepath, "a", newline="") as f:
writer = csv.writer(f)
if write_header:
writer.writerow(["timestamp", "task_id", "type", "cost_usd"])
writer.writerow([
datetime.now(timezone.utc).isoformat(),
task_id, captcha_type, f"{cost:.6f}",
])
def get_monthly_total(self, tenant_id, year_month=None):
"""Get monthly billing total for a tenant."""
if not year_month:
year_month = datetime.now(timezone.utc).strftime("%Y-%m")
filepath = os.path.join(self.log_dir, f"{tenant_id}_{year_month}.csv")
if not os.path.exists(filepath):
return {"solves": 0, "total_cost": 0.0}
total = 0.0
count = 0
with open(filepath, "r") as f:
reader = csv.DictReader(f)
for row in reader:
total += float(row["cost_usd"])
count += 1
return {"solves": count, "total_cost": round(total, 4)}
Per-Tenant API Key Pattern
For larger platforms, give each tenant their own CaptchaAI key:
class MultiKeyService:
"""Use per-tenant API keys for full isolation."""
def __init__(self, default_key=None):
self.tenant_keys = {}
self.default_key = default_key
def register_tenant(self, tenant_id, api_key):
"""Register a tenant's API key."""
self.tenant_keys[tenant_id] = api_key
def solve(self, tenant_id, params):
"""Solve using tenant's own key."""
key = self.tenant_keys.get(tenant_id, self.default_key)
if not key:
raise ValueError(f"No API key for tenant {tenant_id}")
data = {"key": key, "json": 1, **params}
resp = requests.post(
"https://ocr.captchaai.com/in.php", data=data, timeout=30,
)
return resp.json()
def get_tenant_balance(self, tenant_id):
"""Check a tenant's balance."""
key = self.tenant_keys.get(tenant_id)
if not key:
return None
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": key, "action": "getbalance", "json": 1,
}, timeout=10)
data = resp.json()
if data.get("status") == 1:
return float(data["request"])
return None
FAQ
Should each tenant have their own CaptchaAI key?
For small platforms, a shared key with usage tracking works. For enterprise platforms, per-tenant keys provide full billing isolation and let tenants manage their own balance.
How do I handle one tenant exhausting shared balance?
Use per-tenant daily limits and rate limits. Monitor usage and alert when any tenant approaches their allocation.
Can I resell CaptchaAI solving in my SaaS?
Check CaptchaAI's terms regarding reselling. Many SaaS platforms include CAPTCHA solving as a feature of their product with their own pricing.
Related Guides
Build SaaS with CAPTCHA solving — integrate CaptchaAI.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.