API Tutorials

Comment résoudre Cloudflare Turnstile via l'API

Résoudre Cloudflare Turnstile via l'API tient en quatre étapes : extraire le sitekey de la page, envoyer la tâche à un service de résolution, interroger le résultat, puis injecter le token dans le formulaire. Ce guide déroule chacune de ces étapes avec l'API CaptchaAI, en Python et en Node.js.

Turnstile n'est pas un CAPTCHA classique. Il n'affiche presque jamais de défi visible : il observe des signaux du navigateur en arrière-plan et émet un token que le backend du site vérifie. Pour une automatisation — un test QA, un pipeline de scraping autorisé, une tâche planifiée — la méthode reste la même : vous fournissez le sitekey et l'URL exacte de la page, et vous récupérez un token à placer dans le champ cf-turnstile-response.

Si vous découvrez le flux général de l'API, parcourez d'abord le guide de démarrage rapide ; la suite suppose que votre clé API est prête.


Ce dont vous avez besoin

Quatre éléments suffisent pour démarrer :

Élément Détail
Clé API CaptchaAI Depuis votre tableau de bord sur captchaai.com
Sitekey Turnstile Extrait de la page cible (commence par 0x)
URL de la page L'adresse complète, avec https://, où Turnstile s'affiche
Environnement Python 3.7+ ou Node.js 14+

Étape 1 : extraire le sitekey Turnstile

Le sitekey identifie le widget Turnstile côté site. On le trouve presque toujours dans le HTML, dans une balise div ou script :

<div class="cf-turnstile" data-sitekey="0x4AAAAAAAC3DHQFLr1GavNl"></div>

Ou injecté par JavaScript au moment du rendu :

turnstile.render('#widget', {
  sitekey: '0x4AAAAAAAC3DHQFLr1GavNl',
  callback: function(token) { /* ... */ }
});

Trois façons de le récupérer, de la plus rapide à la plus fiable :

  1. DevTools — onglet Elements, cherchez data-sitekey ou cf-turnstile.
  2. Code sourceCtrl+U, puis repérez les chaînes commençant par 0x.
  3. Onglet Network — filtrez sur challenges.cloudflare.com ; le sitekey figure dans les paramètres de requête.

Un sitekey Turnstile commence toujours par 0x et fait environ 22 caractères. C'est ce qui le distingue d'une clé reCAPTCHA, qui commence par 6L : les confondre fait échouer la tâche avant le polling.


Étape 2 : envoyer la tâche à l'API

Envoyez une requête POST vers https://ocr.captchaai.com/in.php avec method=turnstile, la clé, le sitekey et l'URL de la page :

import requests

API_KEY = "YOUR_CAPTCHAAI_KEY"
SITEKEY = "0x4AAAAAAAC3DHQFLr1GavNl"
PAGEURL = "https://example.com/login"

r = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": SITEKEY,
    "pageurl": PAGEURL,
    "json": 1,
})
data = r.json()
if data["status"] != 1:
    raise RuntimeError(f"submit failed: {data}")
task_id = data["request"]
print("task id:", task_id)

Le même appel en Node.js, avec axios :

const axios = require("axios");

const { data } = await axios.post("https://ocr.captchaai.com/in.php", null, {
  params: {
    key: process.env.CAPTCHAAI_KEY,
    method: "turnstile",
    sitekey: "0x4AAAAAAAC3DHQFLr1GavNl",
    pageurl: "https://example.com/login",
    json: 1,
  },
});
if (data.status !== 1) throw new Error(`submit failed: ${JSON.stringify(data)}`);
const taskId = data.request;

La réponse attendue est {"status": 1, "request": "<task_id>"}. Conservez task_id : il sert à interroger le résultat à l'étape suivante. Si status vaut 0, la valeur de request contient le code d'erreur à corriger.


Étape 3 : interroger le résultat (polling)

Cloudflare Turnstile se résout généralement en moins de 10 secondes, mais le token n'est pas disponible dès l'envoi. Le principe : attendre, puis interroger res.php à intervalle régulier.

Prévoyez une première attente de 10 secondes, puis interrogez toutes les 5 secondes, avec un plafond de 40 itérations pour éviter une boucle infinie :

import time

time.sleep(10)
for _ in range(40):
    r = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY,
        "action": "get",
        "id": task_id,
        "json": 1,
    })
    res = r.json()
    if res["status"] == 1:
        token = res["request"]
        break
    if res["request"] != "CAPCHA_NOT_READY":
        raise RuntimeError(f"solver error: {res}")
    time.sleep(5)
else:
    raise TimeoutError("turnstile solving timed out")

print("token (60 premiers caractères) :", token[:60])

Tant que la résolution est en cours, l'API renvoie CAPCHA_NOT_READY : ce n'est pas une erreur, continuez le polling. Tout autre code interrompt la boucle. Le token final est une chaîne Base64 qui commence en général par 0. et fait 400 à 600 caractères.


Étape 4 : injecter le token dans la page

Replacez le token dans le champ caché cf-turnstile-response du formulaire, puis soumettez.

Avec Selenium :

driver.execute_script(
    "document.querySelector('[name=cf-turnstile-response]').value = arguments[0];",
    token,
)
driver.find_element("css selector", "form").submit()

Avec Playwright :

page.evaluate(
    "(t) => document.querySelector('[name=cf-turnstile-response]').value = t",
    token,
)
page.click("button[type=submit]")

En HTTP brut, sans navigateur : ajoutez cf-turnstile-response=<token> au corps application/x-www-form-urlencoded de votre requête POST.

Un token Turnstile reste valide environ 120 à 300 secondes. Utilisez-le immédiatement : passé ce délai, le backend répond timeout-or-duplicate et il faut relancer une résolution.


Exemple Python de bout en bout

Les quatre étapes réunies dans une fonction réutilisable, avec timeouts et gestion d'erreurs :

import os, time, requests

API = "https://ocr.captchaai.com"
KEY = os.environ["CAPTCHAAI_KEY"]

def solve_turnstile(sitekey: str, pageurl: str) -> str:
    r = requests.post(f"{API}/in.php", data={
        "key": KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1,
    }, timeout=30)
    j = r.json()
    if j["status"] != 1:
        raise RuntimeError(f"submit: {j}")
    tid = j["request"]

    time.sleep(10)
    for _ in range(40):
        r = requests.get(f"{API}/res.php", params={
            "key": KEY, "action": "get", "id": tid, "json": 1,
        }, timeout=30)
        j = r.json()
        if j["status"] == 1:
            return j["request"]
        if j["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(f"poll: {j}")
        time.sleep(5)
    raise TimeoutError("timeout")

if __name__ == "__main__":
    print(solve_turnstile("0x4AAAAAAAC3DHQFLr1GavNl", "https://example.com/login"))

Passez la clé API par variable d'environnement plutôt qu'en dur : c'est le réflexe minimal pour ne pas exposer un identifiant dans un dépôt Git.


Codes d'erreur à connaître

La plupart des échecs viennent d'un paramètre mal renseigné, pas du solveur :

Code Signification Action
ERROR_WRONG_USER_KEY Format de clé API invalide Vérifiez que CAPTCHAAI_KEY est complet
ERROR_KEY_DOES_NOT_EXIST Clé API introuvable Recopiez la clé depuis le tableau de bord
ERROR_ZERO_BALANCE Solde nul Rechargez le solde, puis réessayez
ERROR_PAGEURL Paramètre pageurl manquant Envoyez l'URL complète avec https://
ERROR_CAPTCHA_UNSOLVABLE Échec après plusieurs tentatives Vérifiez la cohérence sitekey/pageurl, réessayez une fois

Ces codes sont communs à toutes les méthodes. Le guide de résolution de reCAPTCHA v2 détaille le même mécanisme de polling.


Quand la résolution échoue

Turnstile est rapide et stable, mais quelques situations réclament un ajustement côté client :

  1. Sitekey dynamique. Certains sites génèrent un nouveau sitekey à chaque visite ; refaites l'extraction avant chaque tâche plutôt que de mettre la valeur en cache.
  2. URL trop précise. Le backend compare l'URL strictement : envoyez le chemin exact, sans query string parasite.
  3. Empreinte TLS. Cloudflare peut rejeter un client selon sa signature TLS. curl_cffi, Playwright ou un vrai navigateur évitent ce rejet.
  4. Token expiré. Consommez le token dans les deux minutes, sinon relancez une résolution.
  5. Qualité du proxy. Les IP datacenter bon marché déclenchent souvent des défis supplémentaires. Préférez des proxys résidentiels ou mobiles.

Dimensionner vos threads

CaptchaAI facture au thread (une résolution en cours), pas à la résolution unitaire, et chaque plan inclut un nombre illimité de résolutions par thread sur le mois. Votre budget dépend donc de votre concurrence, pas de votre volume.

Prenons une équipe QA à Paris ou à Casablanca qui vérifie quelques milliers de parcours protégés par Turnstile chaque jour. Comme Turnstile se résout en moins de 10 secondes, un seul thread absorbe déjà un débit confortable ; le plan BASIC ($15/mois, 5 threads) couvre cette charge. Un pipeline de scraping autorisé, plus parallèle, passera au plan STANDARD ($30/mois, 15 threads). La facturation est en dollars US.

Si votre automatisation collecte des données, gardez le réflexe RGPD : minimisez les données personnelles traitées et restez sur un périmètre autorisé.


Questions fréquentes

Combien de threads faut-il pour résoudre plusieurs Turnstile en parallèle ?

Un thread traite un Turnstile à la fois. Comme la résolution prend moins de 10 secondes, un seul thread suffit à un débit modéré ; le plan BASIC ($15/mois, 5 threads) en couvre 5 en parallèle.

Faut-il un proxy pour résoudre Turnstile via l'API ?

Pas nécessairement. Contrairement aux pages Cloudflare Challenge, la résolution de Turnstile ne réclame pas de paramètre de proxy. Un proxy résidentiel n'est utile que si la page cible durcit ses contrôles.

Que faire quand le backend répond timeout-or-duplicate ?

Le token a expiré ou a déjà été soumis. Résolvez à nouveau et injectez le nouveau token sans attendre : un token Turnstile ne vit que 2 à 5 minutes et ne se réutilise pas.

Turnstile et Cloudflare Challenge, est-ce la même chose ?

Non. Turnstile est un widget intégré à la page, proche d'un reCAPTCHA. Cloudflare Challenge est une page complète « Vérification de votre navigateur » qui s'affiche avant le contenu, et suit une autre méthode d'API.


Pour aller plus loin

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