API Tutorials

Solving CAPTCHAs with Rust and CaptchaAI API

Rust's safety guarantees, zero-cost abstractions, and async ecosystem make it increasingly popular for high-performance scrapers and automation tools. When those tools hit CAPTCHAs, CaptchaAI's HTTP API integrates cleanly through reqwest and tokio.

This guide covers reCAPTCHA v2, Cloudflare Turnstile, and image CAPTCHA solving — with both synchronous (blocking) and async implementations you can embed in any Rust project.


Why Rust for CAPTCHA Automation

  • Memory safety without GC — no crashes, no leaks at scale
  • Async native — tokio + reqwest handle thousands of concurrent solves
  • Type safety — serde serialization catches API errors at compile time
  • Performance — ideal for high-throughput CAPTCHA pipelines
  • Cross-platform — single binary deploys to Linux, macOS, Windows

Prerequisites

Add dependencies to Cargo.toml:

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
base64 = "0.22"
thiserror = "2"

CaptchaAI API Flow

  1. Submit — POST to https://ocr.captchaai.com/in.php → receive task ID
  2. Poll — GET https://ocr.captchaai.com/res.php?action=get&id=TASK_ID → receive token

Type Definitions

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Deserialize)]
struct ApiResponse {
    status: u8,
    request: String,
}

#[derive(Debug, Error)]
enum CaptchaError {
    #[error("API error: {0}")]
    ApiError(String),
    #[error("HTTP error: {0}")]
    HttpError(#[from] reqwest::Error),
    #[error("Timeout waiting for solution")]
    Timeout,
    #[error("Invalid response: {0}")]
    ParseError(String),
}

#[derive(Debug, Clone)]
enum CaptchaType {
    RecaptchaV2 { sitekey: String, page_url: String },
    RecaptchaV3 { sitekey: String, page_url: String, action: String, min_score: f32 },
    Turnstile { sitekey: String, page_url: String },
    ImageBase64 { body: String },
}

use reqwest::Client;
use std::time::Duration;

struct CaptchaSolver {
    api_key: String,
    client: Client,
    base_url: String,
    poll_interval: Duration,
    max_wait: Duration,
}

impl CaptchaSolver {
    fn new(api_key: &str) -> Self {
        Self {
            api_key: api_key.to_string(),
            client: Client::builder()
                .timeout(Duration::from_secs(30))
                .build()
                .expect("Failed to create HTTP client"),
            base_url: "https://ocr.captchaai.com".to_string(),
            poll_interval: Duration::from_secs(5),
            max_wait: Duration::from_secs(300),
        }
    }

    async fn solve(&self, captcha: CaptchaType) -> Result<String, CaptchaError> {
        let task_id = self.submit(captcha).await?;
        self.poll(&task_id).await
    }

    async fn submit(&self, captcha: CaptchaType) -> Result<String, CaptchaError> {
        let mut params = vec![
            ("key".to_string(), self.api_key.clone()),
            ("json".to_string(), "1".to_string()),
        ];

        match captcha {
            CaptchaType::RecaptchaV2 { sitekey, page_url } => {
                params.push(("method".to_string(), "userrecaptcha".to_string()));
                params.push(("googlekey".to_string(), sitekey));
                params.push(("pageurl".to_string(), page_url));
            }
            CaptchaType::RecaptchaV3 { sitekey, page_url, action, min_score } => {
                params.push(("method".to_string(), "userrecaptcha".to_string()));
                params.push(("googlekey".to_string(), sitekey));
                params.push(("pageurl".to_string(), page_url));
                params.push(("version".to_string(), "v3".to_string()));
                params.push(("action".to_string(), action));
                params.push(("min_score".to_string(), min_score.to_string()));
            }
            CaptchaType::Turnstile { sitekey, page_url } => {
                params.push(("method".to_string(), "turnstile".to_string()));
                params.push(("key".to_string(), sitekey));
                params.push(("pageurl".to_string(), page_url));
            }
            CaptchaType::ImageBase64 { body } => {
                params.push(("method".to_string(), "base64".to_string()));
                params.push(("body".to_string(), body));
            }
        }

        let response: ApiResponse = self.client
            .post(format!("{}/in.php", self.base_url))
            .form(&params)
            .send()
            .await?
            .json()
            .await?;

        if response.status != 1 {
            return Err(CaptchaError::ApiError(response.request));
        }

        Ok(response.request)
    }

    async fn poll(&self, task_id: &str) -> Result<String, CaptchaError> {
        let start = std::time::Instant::now();

        loop {
            if start.elapsed() > self.max_wait {
                return Err(CaptchaError::Timeout);
            }

            tokio::time::sleep(self.poll_interval).await;

            let response: ApiResponse = self.client
                .get(format!("{}/res.php", self.base_url))
                .query(&[
                    ("key", self.api_key.as_str()),
                    ("action", "get"),
                    ("id", task_id),
                    ("json", "1"),
                ])
                .send()
                .await?
                .json()
                .await?;

            if response.request == "CAPCHA_NOT_READY" {
                continue;
            }

            if response.status != 1 {
                return Err(CaptchaError::ApiError(response.request));
            }

            return Ok(response.request);
        }
    }

    async fn check_balance(&self) -> Result<f64, CaptchaError> {
        let response: ApiResponse = self.client
            .get(format!("{}/res.php", self.base_url))
            .query(&[
                ("key", self.api_key.as_str()),
                ("action", "getbalance"),
                ("json", "1"),
            ])
            .send()
            .await?
            .json()
            .await?;

        response.request.parse::<f64>()
            .map_err(|e| CaptchaError::ParseError(e.to_string()))
    }
}

Usage

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let solver = CaptchaSolver::new("YOUR_API_KEY");

    // Check balance
    let balance = solver.check_balance().await?;
    println!("Balance: ${:.2}", balance);

    // Solve reCAPTCHA v2
    let token = solver.solve(CaptchaType::RecaptchaV2 {
        sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-".to_string(),
        page_url: "https://example.com/login".to_string(),
    }).await?;

    println!("Token: {}...", &token[..50.min(token.len())]);

    // Solve Turnstile
    let turnstile_token = solver.solve(CaptchaType::Turnstile {
        sitekey: "0x4AAAAAAAB5...".to_string(),
        page_url: "https://example.com/form".to_string(),
    }).await?;

    println!("Turnstile: {}...", &turnstile_token[..50.min(turnstile_token.len())]);

    Ok(())
}

Solving Image CAPTCHAs

use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use std::fs;

async fn solve_image_captcha(
    solver: &CaptchaSolver,
    image_path: &str,
) -> Result<String, CaptchaError> {
    let image_bytes = fs::read(image_path)
        .map_err(|e| CaptchaError::ParseError(e.to_string()))?;
    let encoded = STANDARD.encode(&image_bytes);

    solver.solve(CaptchaType::ImageBase64 { body: encoded }).await
}

// Usage
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let solver = CaptchaSolver::new("YOUR_API_KEY");
    let text = solve_image_captcha(&solver, "captcha.png").await?;
    println!("Image text: {}", text);
    Ok(())
}

Concurrent Solving with Tokio

Solve multiple CAPTCHAs in parallel:

use futures::future::join_all;

async fn solve_batch(
    solver: &CaptchaSolver,
    tasks: Vec<CaptchaType>,
) -> Vec<Result<String, CaptchaError>> {
    let futures: Vec<_> = tasks.into_iter()
        .map(|task| solver.solve(task))
        .collect();

    join_all(futures).await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let solver = CaptchaSolver::new("YOUR_API_KEY");

    let tasks = vec![
        CaptchaType::RecaptchaV2 {
            sitekey: "KEY_A".to_string(),
            page_url: "https://site-a.com".to_string(),
        },
        CaptchaType::RecaptchaV2 {
            sitekey: "KEY_B".to_string(),
            page_url: "https://site-b.com".to_string(),
        },
        CaptchaType::Turnstile {
            sitekey: "KEY_C".to_string(),
            page_url: "https://site-c.com".to_string(),
        },
    ];

    let results = solve_batch(&solver, tasks).await;

    for (i, result) in results.iter().enumerate() {
        match result {
            Ok(token) => println!("Task {}: {:.50}...", i, token),
            Err(e) => eprintln!("Task {}: {}", i, e),
        }
    }

    Ok(())
}

Error Handling with Retry

use std::time::Duration;

async fn solve_with_retry(
    solver: &CaptchaSolver,
    captcha: CaptchaType,
    max_retries: u32,
) -> Result<String, CaptchaError> {
    let retryable = |err: &CaptchaError| -> bool {
        match err {
            CaptchaError::ApiError(msg) => {
                msg.contains("ERROR_NO_SLOT_AVAILABLE")
                    || msg.contains("ERROR_CAPTCHA_UNSOLVABLE")
            }
            CaptchaError::Timeout => true,
            CaptchaError::HttpError(_) => true,
            _ => false,
        }
    };

    let mut last_error = CaptchaError::Timeout;

    for attempt in 0..=max_retries {
        if attempt > 0 {
            let delay = Duration::from_secs(2u64.pow(attempt) + rand::random::<u64>() % 3);
            eprintln!("Retry {}/{} after {:?}", attempt, max_retries, delay);
            tokio::time::sleep(delay).await;
        }

        match solver.solve(captcha.clone()).await {
            Ok(token) => return Ok(token),
            Err(e) if retryable(&e) => {
                last_error = e;
                continue;
            }
            Err(e) => return Err(e),
        }
    }

    Err(last_error)
}

Submitting Solved Tokens

use reqwest::Client;
use std::collections::HashMap;

async fn submit_form_with_token(
    url: &str,
    token: &str,
    form_data: HashMap<&str, &str>,
) -> Result<String, reqwest::Error> {
    let client = Client::new();

    let mut params = form_data;
    params.insert("g-recaptcha-response", token);

    let response = client
        .post(url)
        .form(&params)
        .send()
        .await?;

    response.text().await
}

// Usage
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let solver = CaptchaSolver::new("YOUR_API_KEY");

    let token = solver.solve(CaptchaType::RecaptchaV2 {
        sitekey: "SITEKEY".to_string(),
        page_url: "https://example.com/login".to_string(),
    }).await?;

    let mut form = HashMap::new();
    form.insert("username", "user@example.com");
    form.insert("password", "password123");

    let result = submit_form_with_token(
        "https://example.com/login",
        &token,
        form,
    ).await?;

    println!("Response: {}", &result[..200.min(result.len())]);
    Ok(())
}

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Invalid API key Verify key at dashboard
ERROR_ZERO_BALANCE No funds Top up account
ERROR_NO_SLOT_AVAILABLE Server busy Retry after 5 seconds
reqwest::Error — TLS Certificate issues Update rustls or use native-tls feature
Compile error on clone() CaptchaType not Clone Add #[derive(Clone)] to enum
Slow polling Default interval too short Increase poll_interval to 5-10s

FAQ

Does CaptchaAI have a Rust crate?

CaptchaAI uses a REST API that works with any HTTP client. The reqwest + serde combination shown here gives you idiomatic Rust integration.

Should I use async or blocking?

Use async (tokio + reqwest) for any production use. The blocking API is fine for simple CLI tools but can't handle concurrent solves efficiently.

How many CAPTCHAs can I solve concurrently?

Tokio can handle thousands of concurrent futures. CaptchaAI's API is the bottleneck — start with 10-20 concurrent solves and monitor response times.

Can I use this in a web server (Actix, Axum)?

Yes. The CaptchaSolver is Send + Sync and works inside Actix or Axum handlers. Wrap it in Arc for shared ownership across handlers.



Build blazing-fast CAPTCHA automation in Rust — get your API key and start solving.

Discussions (0)

No comments yet.

Related Posts

Tutorials CAPTCHA Solving Fallback Chains
Implement fallback chains for CAPTCHA solving with Captcha AI.

Implement fallback chains for CAPTCHA solving with Captcha AI. Cascade through solver methods, proxy pools, an...

Automation Python reCAPTCHA v2
Apr 06, 2026
API Tutorials Solving CAPTCHAs with Kotlin and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Kotlin using Captcha AI's HTTP API with Ok Http, Ktor client, and coroutines.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Kotlin using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 06, 2026
API Tutorials Solving CAPTCHAs with Swift and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with URLSession, async/await, and Alamofire.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Apr 05, 2026
API Tutorials CaptchaAI API Latency Optimization: Faster Solves
Reduce CAPTCHA solve latency with Captcha AI by optimizing poll intervals, connection pooling, prefetching, and proxy selection.

Reduce CAPTCHA solve latency with Captcha AI by optimizing poll intervals, connection pooling, prefetching, an...

Automation Python reCAPTCHA v2
Feb 27, 2026
API Tutorials Bash Script + cURL + CaptchaAI: Shell CAPTCHA Automation
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs using Bash scripts and c URL with Captcha AI's HTTP API for shell-based automation.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs using Bash scripts and c URL with Captcha...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 20, 2026
API Tutorials Solving CAPTCHAs with Ruby and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Ruby using Captcha AI's HTTP API with net/http, Faraday, and HTTParty.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Ruby using Captcha AI's HTTP API with n...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 17, 2026
API Tutorials Solving CAPTCHAs with Dart for Flutter Applications
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Dart using Captcha AI's HTTP API with the http package, dio, and Flutter integration.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Dart using Captcha AI's HTTP API with t...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 11, 2026
API Tutorials Solving CAPTCHAs with Perl and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Perl using Captcha AI's HTTP API with LWP User Agent, HTTP Tiny, and Mojo User Agent.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Perl using Captcha AI's HTTP API with L...

Automation reCAPTCHA v2 Cloudflare Turnstile
Feb 23, 2026
API Tutorials Solving CAPTCHAs with Scala and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Scala using Captcha AI's HTTP API with sttp, Akka HTTP, and Scala Futures.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Scala using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Feb 08, 2026
API Tutorials Type-Safe CaptchaAI Client with TypeScript Generics
Build a type-safe Captcha AI API client in Type Script using generics, discriminated unions, and builder patterns for CAPTCHA types.

Build a type-safe Captcha AI API client in Type Script using generics, discriminated unions, and builder patte...

Automation reCAPTCHA v2 Cloudflare Turnstile
Feb 05, 2026
API Tutorials Case-Sensitive CAPTCHA API Parameter Guide
How to use the regsense parameter for case-sensitive CAPTCHA solving with Captcha AI.

How to use the regsense parameter for case-sensitive CAPTCHA solving with Captcha AI. Covers when to use, comm...

Python Web Scraping Image OCR
Apr 09, 2026
API Tutorials How to Solve reCAPTCHA v2 Enterprise with Python
Solve re CAPTCHA v 2 Enterprise using Python and Captcha AI API.

Solve re CAPTCHA v 2 Enterprise using Python and Captcha AI API. Complete guide with sitekey extraction, task...

Automation Python reCAPTCHA v2
Apr 08, 2026
API Tutorials Image CAPTCHA Base64 Encoding Best Practices
Best practices for base 64 encoding CAPTCHA images before submitting to Captcha AI.

Best practices for base 64 encoding CAPTCHA images before submitting to Captcha AI. Covers format, quality, si...

Python Web Scraping Image OCR
Apr 06, 2026