API Tutorials

Vérification du solde CaptchaAI et intégration de la recharge automatique

Le solde de votre compte CaptchaAI se lit en un seul appel GET vers res.php avec action=getbalance : la réponse est un montant en dollars US, par exemple 12.345. C'est la vérification qui évite le pire scénario d'un pipeline d'automatisation : un lot de résolutions qui échoue en pleine nuit parce que le compte est tombé à zéro sans que personne ne l'ait vu venir.

Ce guide couvre trois besoins qui vont ensemble : lire le solde, prévenir dès qu'il passe sous un seuil, et suivre la consommation dans le temps pour anticiper la recharge. Aucun de ces mécanismes ne débite votre carte à votre place — l'API ne fait pas de paiement automatique — mais ils vous laissent le temps de recharger avant que le service ne s'arrête.

Bon à savoir : la facturation CaptchaAI se fait en dollars US et les abonnements sont facturés par thread simultané (BASIC à $15/mois, 5 threads, jusqu'à VIP-3 à $7,500/mois, 5 000 threads). Le montant renvoyé par getbalance est votre solde de crédits en USD ; ne le convertissez pas en euros dans vos tableaux de bord, gardez la même unité que la console.


Récupérer le solde avec l'endpoint getbalance

L'appel de base tient en quelques lignes. On interroge res.php, on demande une réponse json, et on lit le champ request :

import requests

API_KEY = "YOUR_API_KEY"

resp = requests.get("https://ocr.captchaai.com/res.php", params={
    "key": API_KEY,
    "action": "getbalance",
    "json": 1,
})

data = resp.json()
balance = float(data["request"])
print(f"Balance: ${balance:.2f}")

La réponse renvoyée par l'API a cette forme :

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

Le champ request arrive sous forme de chaîne : convertissez-le en float avant tout calcul, sinon une comparaison de seuil comme "12.345" < 5 se comportera de façon inattendue.


Contrôler le solde avant de lancer un pipeline

Le contrôle le plus rentable est celui qui bloque le démarrage quand le solde est insuffisant. Plutôt que de découvrir la panne après 200 requêtes envoyées, on vérifie une fois, et on interrompt le processus si le montant passe sous un plancher défini :

import requests
import sys


def check_balance(api_key, min_required=1.0):
    """Check balance and abort if too low."""
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": api_key,
        "action": "getbalance",
        "json": 1,
    })
    data = resp.json()

    if data.get("status") != 1:
        print(f"Balance check failed: {data.get('request')}")
        return None

    balance = float(data["request"])
    print(f"Current balance: ${balance:.2f}")

    if balance < min_required:
        print(f"WARNING: Balance ${balance:.2f} below minimum ${min_required:.2f}")
        return None

    return balance


# Usage
API_KEY = "YOUR_API_KEY"
balance = check_balance(API_KEY, min_required=5.0)

if balance is None:
    print("Insufficient balance. Add funds before running pipeline.")
    sys.exit(1)

print(f"Balance OK (${balance:.2f}). Starting pipeline...")

Calez min_required sur la taille de votre lot : un pipeline qui va enchaîner des milliers de résolutions mérite un plancher plus haut qu'un script ponctuel. En cas de doute, sys.exit(1) fait échouer proprement le job dans votre orchestrateur (cron, CI, worker) plutôt que de le laisser consommer un solde déjà exsangue.


Surveiller le solde en continu et déclencher des alertes

Pour un service qui tourne en permanence, le contrôle ponctuel ne suffit pas : il faut une boucle de surveillance qui interroge le solde à intervalle régulier et alerte une seule fois quand le seuil est franchi. La classe ci-dessous garde un historique, calcule un rythme de dépense et estime le temps restant avant épuisement :

import requests
import time
import smtplib
from email.message import EmailMessage


class BalanceMonitor:
    """Monitor CaptchaAI balance and send alerts."""

    def __init__(self, api_key, alert_threshold=5.0, check_interval=300):
        self.api_key = api_key
        self.alert_threshold = alert_threshold
        self.check_interval = check_interval  # seconds
        self.base_url = "https://ocr.captchaai.com"
        self.history = []
        self.alerted = False

    def get_balance(self):
        resp = requests.get(f"{self.base_url}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        }, timeout=10)
        data = resp.json()
        return float(data["request"])

    def check_and_alert(self):
        balance = self.get_balance()
        self.history.append({
            "time": time.time(),
            "balance": balance,
        })

        print(f"Balance: ${balance:.2f}")

        if balance < self.alert_threshold and not self.alerted:
            self.send_alert(balance)
            self.alerted = True
        elif balance >= self.alert_threshold:
            self.alerted = False

        return balance

    def send_alert(self, balance):
        """Send low-balance alert. Override for your notification system."""
        print(f"ALERT: Balance low! ${balance:.2f} < ${self.alert_threshold:.2f}")
        # Add your notification logic:
        # - Email, Slack webhook, SMS, etc.

    def get_spending_rate(self, hours=1):
        """Calculate spending rate over the last N hours."""
        cutoff = time.time() - (hours * 3600)
        recent = [h for h in self.history if h["time"] > cutoff]

        if len(recent) < 2:
            return 0.0

        spent = recent[0]["balance"] - recent[-1]["balance"]
        return max(0.0, spent)

    def estimate_remaining_hours(self):
        """Estimate how many hours until balance runs out."""
        rate = self.get_spending_rate(hours=1)
        if rate <= 0:
            return float("inf")

        balance = self.history[-1]["balance"] if self.history else 0
        return balance / rate

    def run(self):
        """Run continuous monitoring."""
        print(f"Monitoring balance (alert at ${self.alert_threshold:.2f})")
        while True:
            try:
                self.check_and_alert()
                rate = self.get_spending_rate()
                remaining = self.estimate_remaining_hours()
                print(f"  Spending: ${rate:.2f}/hr, ~{remaining:.1f}hrs remaining")
            except Exception as e:
                print(f"Monitor error: {e}")
            time.sleep(self.check_interval)


# Usage
monitor = BalanceMonitor(
    api_key="YOUR_API_KEY",
    alert_threshold=5.0,
    check_interval=300,  # Check every 5 minutes
)
monitor.run()

Le drapeau alerted est la partie importante : sans lui, une surveillance toutes les 5 minutes vous inonderait de notifications tant que le solde reste bas. Ici, vous recevez une alerte au franchissement du seuil, puis le silence jusqu'à ce que le solde repasse au-dessus et qu'un nouveau franchissement puisse re-déclencher.


Envoyer les alertes de solde faible vers Slack

Un print dans les logs ne réveille personne. Router l'alerte vers le canal Slack de l'équipe la rend actionnable là où vous travaillez déjà. Cette fonction s'insère directement dans send_alert :

import requests


def send_slack_alert(webhook_url, balance, threshold):
    """Send balance alert to Slack channel."""
    payload = {
        "text": f":warning: CaptchaAI balance low!",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"*CaptchaAI Balance Alert*\n"
                        f"Current balance: *${balance:.2f}*\n"
                        f"Alert threshold: ${threshold:.2f}\n"
                        f"Action: Add funds at captchaai.com"
                    ),
                },
            },
        ],
    }
    requests.post(webhook_url, json=payload)


# Add to BalanceMonitor.send_alert():
# send_slack_alert(SLACK_WEBHOOK, balance, self.alert_threshold)

Le même schéma fonctionne avec un webhook e-mail, un SMS ou une alerte PagerDuty : seul le corps du message change. Gardez l'URL du webhook dans une variable d'environnement, jamais en dur dans le code versionné.


Suivre la consommation au fil du temps

Une alerte vous prévient du présent ; un journal vous montre la tendance. En enregistrant le solde à intervalles réguliers dans un fichier CSV, vous obtenez la dépense quotidienne, hebdomadaire et mensuelle, et vous pouvez prévoir la prochaine recharge au lieu de la subir :

import csv
import datetime


class SpendingTracker:
    """Track CaptchaAI spending over time."""

    def __init__(self, api_key, log_file="captchaai_spending.csv"):
        self.api_key = api_key
        self.log_file = log_file
        self._init_log()

    def _init_log(self):
        try:
            with open(self.log_file, "r") as f:
                pass
        except FileNotFoundError:
            with open(self.log_file, "w", newline="") as f:
                writer = csv.writer(f)
                writer.writerow(["timestamp", "balance"])

    def record_balance(self):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        })
        balance = float(resp.json()["request"])

        with open(self.log_file, "a", newline="") as f:
            writer = csv.writer(f)
            writer.writerow([
                datetime.datetime.utcnow().isoformat(),
                f"{balance:.4f}",
            ])
        return balance

    def get_daily_spending(self):
        """Calculate today's spending from log."""
        today = datetime.date.today().isoformat()
        balances = []

        with open(self.log_file, "r") as f:
            reader = csv.DictReader(f)
            for row in reader:
                if row["timestamp"].startswith(today):
                    balances.append(float(row["balance"]))

        if len(balances) < 2:
            return 0.0
        return balances[0] - balances[-1]

    def summary(self):
        """Print spending summary."""
        balance = self.record_balance()
        daily = self.get_daily_spending()
        print(f"Current balance: ${balance:.2f}")
        print(f"Spent today: ${daily:.2f}")
        if daily > 0:
            print(f"Daily rate: ${daily:.2f}/day")
            print(f"Days remaining: {balance / daily:.1f}")


# Usage
tracker = SpendingTracker("YOUR_API_KEY")
tracker.summary()

Côté conformité, ce journal reste sobre : il ne contient qu'un horodatage et un montant, aucune donnée personnelle. C'est exactement la logique de minimisation attendue par le RGPD — vous n'enregistrez que ce qui sert au pilotage financier. Si vous déportez ce suivi sur un worker OVHcloud ou Scaleway, un simple fichier CSV ou une table dédiée suffit ; inutile d'y agréger des identifiants clients.


Intégrer le contrôle du solde au solveur

L'étape finale consiste à fondre la vérification dans le solveur lui-même, sans payer un appel getbalance à chaque résolution. La stratégie : ne re-contrôler que toutes les 50 résolutions ou toutes les 5 minutes, et lever une exception claire si le solde devient insuffisant :

import requests
import time


class BalanceAwareSolver:
    """Solver that checks balance before solving."""

    def __init__(self, api_key, min_balance=1.0):
        self.api_key = api_key
        self.base_url = "https://ocr.captchaai.com"
        self.min_balance = min_balance
        self.last_balance_check = 0
        self.cached_balance = None
        self.solves_since_check = 0

    def solve(self, method, **params):
        """Solve with balance pre-check."""
        # Check balance every 50 solves or every 5 minutes
        if self._should_check_balance():
            balance = self._get_balance()
            if balance < self.min_balance:
                raise RuntimeError(
                    f"Balance too low: ${balance:.2f} "
                    f"(minimum: ${self.min_balance:.2f})"
                )

        return self._do_solve(method, **params)

    def _should_check_balance(self):
        elapsed = time.time() - self.last_balance_check
        return elapsed > 300 or self.solves_since_check >= 50

    def _get_balance(self):
        resp = requests.get(f"{self.base_url}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        })
        self.cached_balance = float(resp.json()["request"])
        self.last_balance_check = time.time()
        self.solves_since_check = 0
        return self.cached_balance

    def _do_solve(self, method, **params):
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)
        resp = requests.post(f"{self.base_url}/in.php", data=data)
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = result.json()
            if data["request"] != "CAPCHA_NOT_READY":
                self.solves_since_check += 1
                return data["request"]

        raise TimeoutError("Solve timeout")


# Usage
solver = BalanceAwareSolver("YOUR_API_KEY", min_balance=2.0)

try:
    token = solver.solve("userrecaptcha", googlekey="KEY", pageurl="https://example.com")
except RuntimeError as e:
    print(f"Balance issue: {e}")

Ce cache est un compromis assumé : entre deux contrôles, vous acceptez de fonctionner sur une valeur légèrement périmée pour ne pas gonfler le nombre d'appels. Avec un intervalle de 50 résolutions et un min_balance supérieur au coût de ces 50 résolutions, vous ne risquez jamais de passer réellement en négatif.


Dépannage

Problème Cause probable Correctif
Le solde renvoie 0 Compte neuf ou fonds épuisés Rechargez le compte sur captchaai.com
ERROR_WRONG_USER_KEY Clé API invalide ou mal copiée Recopiez la clé depuis le tableau de bord
La vérification du solde expire Problème réseau ou latence Ajoutez timeout=10 à la requête et prévoyez un retry
Le solde ne se met pas à jour Valeur mise en cache trop longtemps Forcez un nouveau contrôle, réduisez l'intervalle
status différent de 1 Réponse d'erreur, pas un montant Testez data["status"] avant de convertir en float

Questions fréquentes

Que signifie la valeur renvoyée par getbalance ?

C'est votre solde de crédits, exprimé en dollars US, sous forme de chaîne (par exemple 12.345). Convertissez-la en float avant de comparer un seuil, et gardez l'unité USD telle quelle sans la convertir en euros.

À quel rythme interroger le solde sans gaspiller d'appels ?

Toutes les 5 à 10 minutes pour un pipeline de production, ou toutes les 50 à 100 résolutions. Vérifier avant chaque résolution double vos appels API pour un bénéfice nul : mettez la valeur en cache entre deux contrôles.

La recharge peut-elle être totalement automatique ?

Non : l'API ne déclenche aucun paiement. Le schéma « recharge automatique » consiste ici à recevoir une alerte de solde faible via BalanceMonitor, puis à recharger manuellement ou via votre système de facturation avant l'épuisement.

Que faire si le solde tombe à zéro en pleine exécution ?

Le solveur commence à renvoyer des erreurs au lieu de tokens. Le min_balance de BalanceAwareSolver lève une RuntimeError avant d'en arriver là ; attrapez-la pour mettre le pipeline en pause proprement, alerter, puis reprendre après recharge.

Le suivi des dépenses pose-t-il un problème RGPD ?

Non, tant que le journal se limite à un horodatage et à un montant. Il ne contient aucune donnée personnelle : c'est de la donnée de pilotage financier, pas de la donnée client à protéger.


Guides connexes


Gardez un œil sur vos dépenses : ouvrez un compte CaptchaAI et suivez chaque résolution.

Les commentaires sont désactivés pour cet article.