API Tutorials

Automatiser la résolution CAPTCHA en Bash et cURL avec CaptchaAI

Pour résoudre un CAPTCHA depuis un script shell, deux outils déjà présents sur votre serveur suffisent : cURL pour appeler l'API CaptchaAI et jq pour lire la réponse JSON. Aucun runtime Python ou Node.js, aucune dépendance lourde.

C'est exactement ce qu'il faut quand le CAPTCHA se dresse au milieu d'un cron ou d'un job CI/CD : vous récupérez le token et le pipeline continue. Ce guide couvre reCAPTCHA v2 et v3, Cloudflare Turnstile et les CAPTCHA image, en pur Bash.

Pourquoi cURL suffit pour résoudre un CAPTCHA en shell

L'API CaptchaAI est une API HTTP classique : tâche envoyée en POST, résultat interrogé en GET. cURL fait les deux nativement.

  • Zéro dépendance lourde : cURL et Bash sont déjà présents sur la plupart des environnements Unix et des images Docker de base.
  • Léger : pas de runtime applicatif, pas d'étape d'installation.
  • Compatible cron : parfait pour les tâches planifiées qui franchissent un défi CAPTCHA sans intervention humaine.
  • Prêt pour la CI/CD : fonctionne tel quel dans Docker, GitHub Actions, Jenkins ou GitLab CI.
  • Combinable avec les outils shell : jq, grep, awk, pipes et redirections chaînent la résolution avec le reste du script.

Ce qu'il vous faut avant de commencer

  • Bash 4.0+
  • cURL (installé par défaut sur Linux et macOS)
  • jq pour analyser le JSON : apt install jq ou brew install jq
  • Une clé API CaptchaAI (créez un compte ici)

La facturation CaptchaAI se fait par thread simultané, pas par résolution : un script shell mono-thread reste largement dans les limites du plan d'entrée BASIC ($15/mois, 5 threads), qui inclut des résolutions illimitées dans le mois.

Les deux fonctions au cœur du script : envoi et interrogation

Toute intégration CaptchaAI en shell repose sur deux opérations : envoyer la tâche, puis interroger l'API jusqu'à obtenir le token. Écrivez-les une fois, réutilisez-les partout.

Envoyer la tâche à l'API

L'endpoint in.php reçoit la tâche et renvoie un identifiant, lu via jq dans les champs status et request de la réponse JSON.

#!/bin/bash

CAPTCHAAI_URL="https://ocr.captchaai.com"

submit_task() {
    local api_key="$1"
    shift
    local params=("$@")

    local response
    response=$(curl -s -X POST "${CAPTCHAAI_URL}/in.php" \
        -d "key=${api_key}" \
        -d "json=1" \
        "${params[@]}")

    local status
    status=$(echo "$response" | jq -r '.status')
    local request
    request=$(echo "$response" | jq -r '.request')

    if [ "$status" != "1" ]; then
        echo "ERROR: Submit failed: $request" >&2
        return 1
    fi

    echo "$request"
}

Interroger le résultat (polling)

La résolution n'est pas instantanée. On interroge res.php tant que la réponse vaut CAPCHA_NOT_READY, avec un délai d'expiration pour ne pas boucler à l'infini. Un intervalle de 5 secondes reste réactif sans marteler l'API.

poll_result() {
    local api_key="$1"
    local task_id="$2"
    local max_wait="${3:-300}"
    local interval="${4:-5}"

    local elapsed=0

    while [ "$elapsed" -lt "$max_wait" ]; do
        sleep "$interval"
        elapsed=$((elapsed + interval))

        local response
        response=$(curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=get&id=${task_id}&json=1")

        local status
        status=$(echo "$response" | jq -r '.status')
        local request
        request=$(echo "$response" | jq -r '.request')

        if [ "$request" = "CAPCHA_NOT_READY" ]; then
            echo "Waiting... (${elapsed}s/${max_wait}s)" >&2
            continue
        fi

        if [ "$status" != "1" ]; then
            echo "ERROR: Solve failed: $request" >&2
            return 1
        fi

        echo "$request"
        return 0
    done

    echo "ERROR: Timeout after ${max_wait}s" >&2
    return 1
}

Résoudre reCAPTCHA v2 en Bash

reCAPTCHA v2 est le cas le plus fréquent. Fournissez la clé du site (googlekey) et l'URL de la page via la méthode userrecaptcha ; vous récupérez un token attendu dans le champ g-recaptcha-response.

solve_recaptcha_v2() {
    local api_key="$1"
    local site_url="$2"
    local sitekey="$3"

    echo "Submitting reCAPTCHA v2..." >&2
    local task_id
    task_id=$(submit_task "$api_key" \
        -d "method=userrecaptcha" \
        -d "googlekey=${sitekey}" \
        -d "pageurl=${site_url}")

    if [ $? -ne 0 ]; then return 1; fi
    echo "Task ID: $task_id" >&2

    echo "Polling for solution..." >&2
    local token
    token=$(poll_result "$api_key" "$task_id")

    if [ $? -ne 0 ]; then return 1; fi
    echo "$token"
}

# Usage
API_KEY="YOUR_API_KEY"
TOKEN=$(solve_recaptcha_v2 "$API_KEY" \
    "https://example.com/login" \
    "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")

echo "Token: ${TOKEN:0:50}..."

Résoudre Cloudflare Turnstile

Turnstile suit la même logique avec la méthode turnstile. Le sitekey se passe dans le paramètre key ; le token se réinjecte dans le champ cf-turnstile-response.

solve_turnstile() {
    local api_key="$1"
    local site_url="$2"
    local sitekey="$3"

    local task_id
    task_id=$(submit_task "$api_key" \
        -d "method=turnstile" \
        -d "key=${sitekey}" \
        -d "pageurl=${site_url}")

    if [ $? -ne 0 ]; then return 1; fi

    poll_result "$api_key" "$task_id"
}

# Usage
TOKEN=$(solve_turnstile "$API_KEY" \
    "https://example.com/form" \
    "0x4AAAAAAAB5...")

Résoudre reCAPTCHA v3 avec son action

reCAPTCHA v3 ne demande aucun clic : il renvoie un score associé à une action. Précisez version=v3 et l'action attendue par la page (login, submit, verify…) pour que le token colle au contexte vérifié côté serveur.

solve_recaptcha_v3() {
    local api_key="$1"
    local site_url="$2"
    local sitekey="$3"
    local action="${4:-verify}"

    local task_id
    task_id=$(submit_task "$api_key" \
        -d "method=userrecaptcha" \
        -d "googlekey=${sitekey}" \
        -d "pageurl=${site_url}" \
        -d "version=v3" \
        -d "action=${action}" \

    if [ $? -ne 0 ]; then return 1; fi

    poll_result "$api_key" "$task_id"
}

Résoudre un CAPTCHA image (OCR)

Pour les CAPTCHA image, on encode le fichier en base64 et on l'envoie via la méthode base64 ; le second helper télécharge d'abord l'image depuis une URL.

solve_image_captcha() {
    local api_key="$1"
    local image_path="$2"

    if [ ! -f "$image_path" ]; then
        echo "ERROR: File not found: $image_path" >&2
        return 1
    fi

    local base64_data
    base64_data=$(base64 -w 0 "$image_path" 2>/dev/null || base64 "$image_path")

    local task_id
    task_id=$(submit_task "$api_key" \
        -d "method=base64" \
        --data-urlencode "body=${base64_data}")

    if [ $? -ne 0 ]; then return 1; fi

    poll_result "$api_key" "$task_id"
}

# From URL
solve_image_from_url() {
    local api_key="$1"
    local image_url="$2"
    local tmp_file
    tmp_file=$(mktemp /tmp/captcha_XXXXXX.png)

    curl -s -o "$tmp_file" "$image_url"
    local result
    result=$(solve_image_captcha "$api_key" "$tmp_file")
    rm -f "$tmp_file"

    echo "$result"
}

# Usage
TEXT=$(solve_image_captcha "$API_KEY" "captcha.png")
echo "CAPTCHA text: $TEXT"

Regrouper le tout dans une bibliothèque shell

Rassemblez ces fonctions dans un fichier captchaai.sh à sourcer : envoi, polling, solde, et un helper par type de CAPTCHA.

#!/bin/bash
# CaptchaAI Solver Library
# Source this file: source ./captchaai.sh

CAPTCHAAI_URL="https://ocr.captchaai.com"
CAPTCHAAI_POLL_INTERVAL=5
CAPTCHAAI_MAX_WAIT=300

captchaai_submit() {
    local api_key="$1"; shift
    local response
    response=$(curl -s -X POST "${CAPTCHAAI_URL}/in.php" \
        -d "key=${api_key}" -d "json=1" "$@")
    local status=$(echo "$response" | jq -r '.status')
    local request=$(echo "$response" | jq -r '.request')
    [ "$status" = "1" ] && echo "$request" || { echo "Submit: $request" >&2; return 1; }
}

captchaai_poll() {
    local api_key="$1" task_id="$2" elapsed=0
    while [ "$elapsed" -lt "$CAPTCHAAI_MAX_WAIT" ]; do
        sleep "$CAPTCHAAI_POLL_INTERVAL"
        elapsed=$((elapsed + CAPTCHAAI_POLL_INTERVAL))
        local resp=$(curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=get&id=${task_id}&json=1")
        local req=$(echo "$resp" | jq -r '.request')
        local st=$(echo "$resp" | jq -r '.status')
        [ "$req" = "CAPCHA_NOT_READY" ] && continue
        [ "$st" = "1" ] && { echo "$req"; return 0; }
        echo "Solve: $req" >&2; return 1
    done
    echo "Timeout" >&2; return 1
}

captchaai_balance() {
    local api_key="$1"
    curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=getbalance&json=1" | jq -r '.request'
}

captchaai_recaptcha_v2() {
    local key="$1" url="$2" sk="$3"
    local tid=$(captchaai_submit "$key" -d "method=userrecaptcha" -d "googlekey=$sk" -d "pageurl=$url") || return 1
    captchaai_poll "$key" "$tid"
}

captchaai_turnstile() {
    local key="$1" url="$2" sk="$3"
    local tid=$(captchaai_submit "$key" -d "method=turnstile" -d "key=$sk" -d "pageurl=$url") || return 1
    captchaai_poll "$key" "$tid"
}

captchaai_image() {
    local key="$1" path="$2"
    local b64=$(base64 -w 0 "$path" 2>/dev/null || base64 "$path")
    local tid=$(captchaai_submit "$key" -d "method=base64" --data-urlencode "body=$b64") || return 1
    captchaai_poll "$key" "$tid"
}

Sourcer et utiliser la bibliothèque

Un script appelant tient alors en quelques lignes.

#!/bin/bash
source ./captchaai.sh

API_KEY="YOUR_API_KEY"

# Check balance
echo "Balance: $(captchaai_balance "$API_KEY")"

# Solve reCAPTCHA v2
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
    "https://example.com/login" \
    "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")

echo "Token: ${TOKEN:0:50}..."

Soumettre le formulaire avec le token résolu

Résoudre le CAPTCHA n'est que la première moitié du travail : il faut renvoyer le token au formulaire, dans le champ g-recaptcha-response, aux côtés des autres paramètres du POST.

submit_form_with_token() {
    local url="$1"
    local token="$2"
    shift 2

    curl -s -X POST "$url" \
        -d "g-recaptcha-response=${token}" \
        "$@"
}

# Usage: solve then submit
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
    "https://example.com/login" "SITEKEY")

RESPONSE=$(submit_form_with_token "https://example.com/login" \
    "$TOKEN" \
    -d "username=user@example.com" \
    -d "password=password")

echo "Response: $RESPONSE"

Lancer plusieurs résolutions en parallèle

Un script séquentiel résout un CAPTCHA à la fois. Pour traiter plusieurs pages en une passe, lancez chaque résolution dans un job d'arrière-plan (&) puis attendez la fin des processus. Le nombre de threads de votre plan borne le parallélisme.

#!/bin/bash
source ./captchaai.sh

API_KEY="YOUR_API_KEY"
RESULTS_DIR=$(mktemp -d)

# Define tasks
declare -A TASKS
TASKS["site-a"]="https://site-a.com|SITEKEY_A"
TASKS["site-b"]="https://site-b.com|SITEKEY_B"
TASKS["site-c"]="https://site-c.com|SITEKEY_C"

# Launch parallel solves
pids=()
for name in "${!TASKS[@]}"; do
    IFS='|' read -r url sitekey <<< "${TASKS[$name]}"
    (
        token=$(captchaai_recaptcha_v2 "$API_KEY" "$url" "$sitekey" 2>/dev/null)
        if [ $? -eq 0 ]; then
            echo "$token" > "${RESULTS_DIR}/${name}.token"
        else
            echo "FAILED" > "${RESULTS_DIR}/${name}.token"
        fi
    ) &
    pids+=($!)
done

# Wait for all
for pid in "${pids[@]}"; do
    wait "$pid"
done

# Collect results
echo "=== Results ==="
for name in "${!TASKS[@]}"; do
    token=$(cat "${RESULTS_DIR}/${name}.token")
    if [ "$token" = "FAILED" ]; then
        echo "$name: FAILED"
    else
        echo "$name: ${token:0:50}..."
    fi
done

rm -rf "$RESULTS_DIR"

Gérer les erreurs avec un backoff exponentiel

Certaines erreurs sont transitoires (ERROR_NO_SLOT_AVAILABLE, ERROR_CAPTCHA_UNSOLVABLE) et méritent une nouvelle tentative ; réessayer sur une clé invalide ne sert à rien. Un backoff exponentiel espace les tentatives et évite de saturer l'API pendant un pic.

solve_with_retry() {
    local api_key="$1"
    local solve_cmd="$2"
    shift 2
    local max_retries="${1:-3}"

    local retryable_errors=("ERROR_NO_SLOT_AVAILABLE" "ERROR_CAPTCHA_UNSOLVABLE")
    local attempt=0

    while [ "$attempt" -le "$max_retries" ]; do
        if [ "$attempt" -gt 0 ]; then
            local delay=$((2 ** attempt + RANDOM % 3))
            echo "Retry $attempt/$max_retries after ${delay}s..." >&2
            sleep "$delay"
        fi

        local result
        result=$($solve_cmd "$api_key" "${@:2}")

        if [ $? -eq 0 ]; then
            echo "$result"
            return 0
        fi

        # Check if error is retryable
        local is_retryable=0
        for err in "${retryable_errors[@]}"; do
            if echo "$result" | grep -q "$err"; then
                is_retryable=1
                break
            fi
        done

        if [ "$is_retryable" -eq 0 ]; then
            echo "$result"
            return 1
        fi

        attempt=$((attempt + 1))
    done

    echo "Max retries exceeded" >&2
    return 1
}

Planifier l'exécution avec cron

Le cas d'usage le plus courant en shell. Imaginez un worker nocturne sur un VPS OVHcloud (région Gravelines) qui exporte chaque matin un rapport depuis un portail interne : il vérifie le solde, résout le CAPTCHA de connexion, puis télécharge les données. Côté RGPD, journalisez le strict nécessaire — jamais le token complet ni d'identifiants personnels dans /var/log.

# Edit crontab: crontab -e
# Run daily at 8 AM
0 8 * * * /path/to/captcha-automation.sh >> /var/log/captcha.log 2>&1

Exemple de script cron

Vérifiez le solde avant de résoudre : un compte à sec échoue sinon en silence.

#!/bin/bash
source /path/to/captchaai.sh

API_KEY="YOUR_API_KEY"
LOG_FILE="/var/log/captcha-$(date +%Y%m%d).log"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }

# Check balance first
BALANCE=$(captchaai_balance "$API_KEY")
log "Balance: $BALANCE"

if (( $(echo "$BALANCE < 1.0" | bc -l) )); then
    log "WARNING: Low balance!"
    exit 1
fi

# Solve and process
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
    "https://portal.example.com" "SITEKEY")

if [ $? -eq 0 ]; then
    log "Solved successfully"
    # Submit form, download data, etc.
    curl -s "https://portal.example.com/data" \
        -d "g-recaptcha-response=$TOKEN" \
        -o "/data/export-$(date +%Y%m%d).csv"
    log "Data exported"
else
    log "ERROR: Failed to solve CAPTCHA"
    exit 1
fi

Empaqueter dans une image Docker

Pour un déploiement reproductible, une image Alpine avec bash, curl et jq pèse une dizaine de mégaoctets et embarque toute la logique — idéale comme base d'un worker CI/CD.

FROM alpine:3.19

RUN apk add --no-cache bash curl jq

COPY captchaai.sh /usr/local/lib/captchaai.sh
COPY automation.sh /app/automation.sh

RUN chmod +x /app/automation.sh

CMD ["/app/automation.sh"]

Dépannage

Erreur Cause probable Correctif
ERROR_WRONG_USER_KEY Clé API invalide ou mal copiée Vérifiez la clé sur le tableau de bord
ERROR_ZERO_BALANCE Solde épuisé Rechargez le compte
ERROR_NO_SLOT_AVAILABLE Tous vos threads sont occupés Attendez, relancez avec backoff, ou passez à un plan supérieur
CAPCHA_NOT_READY en boucle Résolution encore en cours Normal ; augmentez max_wait si le délai d'expiration arrive trop tôt
curl: (60) SSL certificate Bundle CA manquant Ajoutez --cacert /path/to/ca-bundle.crt, ou -k en test uniquement
jq: command not found jq non installé apt install jq ou brew install jq
base64: invalid option -- 'w' Syntaxe base64 de macOS (BSD) Utilisez base64 file au lieu de base64 -w 0 file
Réponse vide Problème réseau Ajoutez -v à curl pour tracer la requête

FAQ

Quel plan CaptchaAI choisir pour un script cron ?

Pour un cron mono-thread, BASIC ($15/mois, 5 threads) convient : facturation par thread simultané, résolutions illimitées dans le mois. Montez en gamme seulement si vous résolvez beaucoup en parallèle.

Comment interroger le résultat sans marteler l'API ?

Laissez l'intervalle de polling à 5 secondes avec un délai d'expiration (300 s par défaut). Interroger toutes les secondes n'accélère rien et multiplie les requêtes inutiles.

Puis-je lancer plusieurs résolutions en parallèle depuis Bash ?

Oui, avec des jobs d'arrière-plan (&) puis wait. Gardez le nombre de résolutions concurrentes sous le nombre de threads de votre plan, sinon vous rencontrerez ERROR_NO_SLOT_AVAILABLE.

Comment stocker ma clé API sans la coder en dur ?

Passez par une variable d'environnement : export CAPTCHAAI_KEY="..." puis référencez $CAPTCHAAI_KEY. Ne committez jamais une clé dans un fichier versionné.

Le script fonctionne-t-il sur macOS ?

Oui. macOS utilise la version BSD de base64, gérée par le repli base64 -w 0 … || base64 … déjà présent dans le script.

Guides connexes

Résolvez les CAPTCHA directement depuis la ligne de commande : créez votre compte et automatisez avec Bash et cURL.

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