API Tutorials

Solving CAPTCHAs with Scala and CaptchaAI API

Scala developers building data pipelines with Spark, web applications with Play Framework, or distributed systems with Akka encounter CAPTCHAs in web scraping, form automation, and API interactions. CaptchaAI's HTTP API integrates with Scala's rich ecosystem through sttp, Akka HTTP, or Java's HttpClient.

This guide covers reCAPTCHA v2/v3, Cloudflare Turnstile, and image CAPTCHA solving — with both blocking and Future-based async implementations.


Why Scala for CAPTCHA Automation

  • JVM ecosystem — access to all Java libraries plus Scala-native options
  • Functional styleFuture, Try, Either for clean error handling
  • Akka/Pekko — actor-based concurrency for massive parallel solving
  • Spark integration — embed CAPTCHA solving in distributed data pipelines
  • Type safety — sealed traits and case classes model API responses precisely

Prerequisites

build.sbt

libraryDependencies ++= Seq(
  "com.softwaremill.sttp.client3" %% "core" % "3.9.7",
  "com.softwaremill.sttp.client3" %% "circe" % "3.9.7",
  "io.circe" %% "circe-generic" % "0.14.9",
  "io.circe" %% "circe-parser" % "0.14.9"
)

For Akka HTTP:

libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.6.3"
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.9.3"

Data Models

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto._

case class ApiResponse(status: Int, request: String)

object ApiResponse {
  implicit val decoder: Decoder[ApiResponse] = deriveDecoder
}

sealed trait CaptchaTask
object CaptchaTask {
  case class RecaptchaV2(sitekey: String, pageUrl: String) extends CaptchaTask
  case class RecaptchaV3(
    sitekey: String,
    pageUrl: String,
    action: String = "verify",
    minScore: Double = 0.7
  ) extends CaptchaTask
  case class Turnstile(sitekey: String, pageUrl: String) extends CaptchaTask
  case class ImageBase64(body: String) extends CaptchaTask
}

case class CaptchaException(message: String) extends Exception(message)

import sttp.client3._
import io.circe.parser._
import scala.concurrent.duration._

class CaptchaSolver(apiKey: String) {
  private val baseUrl = "https://ocr.captchaai.com"
  private val backend = HttpClientSyncBackend()
  private val pollInterval = 5.seconds
  private val maxWait = 300.seconds

  def solve(task: CaptchaTask): String = {
    val taskId = submit(task)
    poll(taskId)
  }

  def checkBalance(): Double = {
    val response = basicRequest
      .get(uri"$baseUrl/res.php?key=$apiKey&action=getbalance&json=1")
      .send(backend)

    val json = parse(response.body.getOrElse("{}")).getOrElse(
      throw CaptchaException("Invalid response")
    )
    json.hcursor.get[String]("request").getOrElse("0").toDouble
  }

  private def submit(task: CaptchaTask): String = {
    val params: Map[String, String] = Map(
      "key" -> apiKey,
      "json" -> "1"
    ) ++ taskParams(task)

    val response = basicRequest
      .post(uri"$baseUrl/in.php")
      .body(params)
      .send(backend)

    val body = response.body.getOrElse(throw CaptchaException("Empty response"))
    val apiResp = decode[ApiResponse](body).getOrElse(
      throw CaptchaException(s"Parse error: $body")
    )

    if (apiResp.status != 1) throw CaptchaException(s"Submit: ${apiResp.request}")
    apiResp.request
  }

  private def poll(taskId: String): String = {
    val deadline = System.currentTimeMillis() + maxWait.toMillis

    while (System.currentTimeMillis() < deadline) {
      Thread.sleep(pollInterval.toMillis)

      val response = basicRequest
        .get(uri"$baseUrl/res.php?key=$apiKey&action=get&id=$taskId&json=1")
        .send(backend)

      val body = response.body.getOrElse("")
      val apiResp = decode[ApiResponse](body).getOrElse(
        ApiResponse(0, "Parse error")
      )

      if (apiResp.request == "CAPCHA_NOT_READY") ()
      else if (apiResp.status != 1) throw CaptchaException(s"Solve: ${apiResp.request}")
      else return apiResp.request
    }

    throw CaptchaException("Timeout")
  }

  private def taskParams(task: CaptchaTask): Map[String, String] = task match {
    case CaptchaTask.RecaptchaV2(sitekey, pageUrl) =>
      Map("method" -> "userrecaptcha", "googlekey" -> sitekey, "pageurl" -> pageUrl)
    case CaptchaTask.RecaptchaV3(sitekey, pageUrl, action, minScore) =>
      Map(
        "method" -> "userrecaptcha", "googlekey" -> sitekey, "pageurl" -> pageUrl,
        "version" -> "v3", "action" -> action, "min_score" -> minScore.toString
      )
    case CaptchaTask.Turnstile(sitekey, pageUrl) =>
      Map("method" -> "turnstile", "key" -> sitekey, "pageurl" -> pageUrl)
    case CaptchaTask.ImageBase64(body) =>
      Map("method" -> "base64", "body" -> body)
  }

  def close(): Unit = backend.close()
}

Usage

object Main extends App {
  val solver = new CaptchaSolver("YOUR_API_KEY")

  try {
    val balance = solver.checkBalance()
    println(f"Balance: $$$balance%.2f")

    val token = solver.solve(CaptchaTask.RecaptchaV2(
      sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
      pageUrl = "https://example.com/login"
    ))
    println(s"Token: ${token.take(50)}...")

    val turnstile = solver.solve(CaptchaTask.Turnstile(
      sitekey = "0x4AAAAAAAB5...",
      pageUrl = "https://example.com/form"
    ))
    println(s"Turnstile: ${turnstile.take(50)}...")

  } finally {
    solver.close()
  }
}

Async Solver with Futures

import scala.concurrent.{Future, ExecutionContext, Await, blocking}
import scala.concurrent.duration._
import sttp.client3._
import io.circe.parser._

class AsyncCaptchaSolver(apiKey: String)(implicit ec: ExecutionContext) {
  private val baseUrl = "https://ocr.captchaai.com"
  private val backend = HttpClientSyncBackend()

  def solve(task: CaptchaTask): Future[String] = Future {
    blocking {
      val taskId = submit(task)
      poll(taskId)
    }
  }

  def solveBatch(tasks: Seq[CaptchaTask]): Future[Seq[Either[Throwable, String]]] = {
    val futures = tasks.map { task =>
      solve(task).map(Right(_)).recover { case e => Left(e) }
    }
    Future.sequence(futures)
  }

  private def submit(task: CaptchaTask): String = {
    val params = Map("key" -> apiKey, "json" -> "1") ++ taskToParams(task)
    val response = basicRequest.post(uri"$baseUrl/in.php").body(params).send(backend)
    val body = response.body.getOrElse(throw CaptchaException("Empty"))
    val resp = decode[ApiResponse](body).getOrElse(throw CaptchaException(body))
    if (resp.status != 1) throw CaptchaException(s"Submit: ${resp.request}")
    resp.request
  }

  private def poll(taskId: String): String = {
    val deadline = System.currentTimeMillis() + 300000L
    while (System.currentTimeMillis() < deadline) {
      Thread.sleep(5000)
      val response = basicRequest
        .get(uri"$baseUrl/res.php?key=$apiKey&action=get&id=$taskId&json=1")
        .send(backend)
      val body = response.body.getOrElse("")
      decode[ApiResponse](body).toOption match {
        case Some(r) if r.request == "CAPCHA_NOT_READY" => ()
        case Some(r) if r.status == 1 => return r.request
        case Some(r) => throw CaptchaException(s"Solve: ${r.request}")
        case None => ()
      }
    }
    throw CaptchaException("Timeout")
  }

  private def taskToParams(task: CaptchaTask): Map[String, String] = task match {
    case CaptchaTask.RecaptchaV2(sk, url) =>
      Map("method" -> "userrecaptcha", "googlekey" -> sk, "pageurl" -> url)
    case CaptchaTask.RecaptchaV3(sk, url, action, score) =>
      Map("method" -> "userrecaptcha", "googlekey" -> sk, "pageurl" -> url,
        "version" -> "v3", "action" -> action, "min_score" -> score.toString)
    case CaptchaTask.Turnstile(sk, url) =>
      Map("method" -> "turnstile", "key" -> sk, "pageurl" -> url)
    case CaptchaTask.ImageBase64(body) =>
      Map("method" -> "base64", "body" -> body)
  }

  def close(): Unit = backend.close()
}

Usage

import scala.concurrent.ExecutionContext.Implicits.global

object AsyncMain extends App {
  val solver = new AsyncCaptchaSolver("YOUR_API_KEY")

  val tasks = Seq(
    CaptchaTask.RecaptchaV2("KEY_A", "https://site-a.com"),
    CaptchaTask.Turnstile("KEY_B", "https://site-b.com"),
    CaptchaTask.RecaptchaV2("KEY_C", "https://site-c.com"),
  )

  val results = Await.result(solver.solveBatch(tasks), 10.minutes)

  results.zipWithIndex.foreach { case (result, i) =>
    result match {
      case Right(token) => println(s"Task $i: ${token.take(50)}...")
      case Left(error) => println(s"Task $i failed: ${error.getMessage}")
    }
  }

  solver.close()
}

Image CAPTCHA Solving

import java.util.Base64
import java.nio.file.{Files, Paths}

def solveImageFile(solver: CaptchaSolver, path: String): String = {
  val bytes = Files.readAllBytes(Paths.get(path))
  val encoded = Base64.getEncoder.encodeToString(bytes)
  solver.solve(CaptchaTask.ImageBase64(encoded))
}

// Usage
val text = solveImageFile(solver, "captcha.png")
println(s"Text: $text")

Error Handling with Retry

import scala.util.{Try, Success, Failure}

def solveWithRetry(
  solver: CaptchaSolver,
  task: CaptchaTask,
  maxRetries: Int = 3
): String = {
  val retryable = Set("ERROR_NO_SLOT_AVAILABLE", "ERROR_CAPTCHA_UNSOLVABLE")

  var lastError: Throwable = new CaptchaException("No attempts")

  for (attempt <- 0 to maxRetries) {
    if (attempt > 0) {
      val delay = math.pow(2, attempt).toLong * 1000 + (math.random() * 2000).toLong
      println(s"Retry $attempt/$maxRetries after ${delay}ms")
      Thread.sleep(delay)
    }

    Try(solver.solve(task)) match {
      case Success(token) => return token
      case Failure(e: CaptchaException) if retryable.exists(e.message.contains) =>
        lastError = e
      case Failure(e) => throw e
    }
  }

  throw lastError
}

Play Framework Integration

// app/services/CaptchaService.scala
import javax.inject._
import play.api.Configuration

@Singleton
class CaptchaService @Inject()(config: Configuration) {
  private val apiKey = config.get[String]("captchaai.apiKey")
  private val solver = new CaptchaSolver(apiKey)

  def solveRecaptcha(sitekey: String, pageUrl: String): String = {
    solver.solve(CaptchaTask.RecaptchaV2(sitekey, pageUrl))
  }

  def solveTurnstile(sitekey: String, pageUrl: String): String = {
    solver.solve(CaptchaTask.Turnstile(sitekey, pageUrl))
  }
}

// app/controllers/CaptchaController.scala
import javax.inject._
import play.api.mvc._
import play.api.libs.json._

@Singleton
class CaptchaController @Inject()(
  cc: ControllerComponents,
  service: CaptchaService
) extends AbstractController(cc) {

  case class SolveRequest(sitekey: String, pageUrl: String)
  implicit val reads: Reads[SolveRequest] = Json.reads[SolveRequest]

  def solve(): Action[JsValue] = Action(parse.json) { request =>
    request.body.validate[SolveRequest].fold(
      errors => BadRequest(Json.obj("error" -> "Invalid request")),
      req => {
        try {
          val token = service.solveRecaptcha(req.sitekey, req.pageUrl)
          Ok(Json.obj("token" -> token))
        } catch {
          case e: CaptchaException =>
            InternalServerError(Json.obj("error" -> e.message))
        }
      }
    )
  }
}

Spark Integration

import org.apache.spark.sql.{SparkSession, DataFrame}

def scrapeWithCaptcha(
  spark: SparkSession,
  urls: Seq[String],
  apiKey: String,
  sitekey: String
): DataFrame = {
  import spark.implicits._

  val results = urls.map { url =>
    try {
      val solver = new CaptchaSolver(apiKey)
      val token = solver.solve(CaptchaTask.RecaptchaV2(sitekey, url))
      solver.close()

      // Use token to fetch data
      (url, token.take(50), "success")
    } catch {
      case e: Exception => (url, e.getMessage, "failed")
    }
  }

  results.toDF("url", "result", "status")
}

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Invalid API key Verify key at dashboard
ERROR_ZERO_BALANCE No funds Top up account
ConnectionException Network issue Check connectivity, increase timeout
DecodingFailure Unexpected JSON field Add missing fields to case class
ClassNotFoundException Missing dependency Check build.sbt dependencies
TimeoutException Slow solve Increase maxWait duration

FAQ

Does CaptchaAI have a Scala library?

CaptchaAI provides a REST API. The sttp + circe combination shown here gives idiomatic Scala integration.

Should I use blocking or async?

Use blocking (CaptchaSolver) for simple scripts. Use async (AsyncCaptchaSolver with Futures) for production applications, especially with Play or Akka.

Can I use this with Akka Actors?

Yes. Wrap the async solver in an actor or use Akka HTTP's client API directly.

Does this work with Scala 3?

Yes. The code works with both Scala 2.13 and Scala 3. Update circe imports for Scala 3 derivation (derives Decoder).



Add CAPTCHA solving to your Scala applications — get your API key and integrate today.

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 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 Building a Python Wrapper Library for CaptchaAI API
Build a reusable Python wrapper library for the Captcha AI API with type hints, retry logic, context managers, and support for CAPTCHA types.

Build a reusable Python wrapper library for the Captcha AI API with type hints, retry logic, context managers,...

Automation Python reCAPTCHA v2
Jan 31, 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 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 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 Rust and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Rust using Captcha AI's HTTP API with reqwest, tokio async runtime, and serde.

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

Automation reCAPTCHA v2 Cloudflare Turnstile
Jan 24, 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 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
Reference Postman Collection for CaptchaAI API Testing
Import a ready-to-use Postman collection for testing Captcha AI API endpoints — submit, poll, balance check, and error scenarios.

Import a ready-to-use Postman collection for testing all Captcha AI API endpoints — submit, poll, balance chec...

Automation reCAPTCHA v2 Cloudflare Turnstile
Jan 15, 2026
API Tutorials How to Solve reCAPTCHA v2 Callback Using API
how to solve re CAPTCHA v 2 callback implementations using Captcha AI API.

Learn how to solve re CAPTCHA v 2 callback implementations using Captcha AI API. Detect the callback function,...

Automation reCAPTCHA v2 Webhooks
Mar 01, 2026
API Tutorials Solve GeeTest v3 CAPTCHA with Python and CaptchaAI
Step-by-step Python tutorial for solving Gee Test v 3 slide puzzle CAPTCHAs using the Captcha AI API.

Step-by-step Python tutorial for solving Gee Test v 3 slide puzzle CAPTCHAs using the Captcha AI API. Includes...

Automation Python Testing
Mar 23, 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