API Tutorials

Solving CAPTCHAs with Kotlin and CaptchaAI API

Kotlin's concise syntax, null safety, and coroutine support make it a strong choice for automation tools, Android apps, and backend services. When these applications encounter CAPTCHAs, CaptchaAI's HTTP API integrates seamlessly through OkHttp or Ktor client.

This guide covers reCAPTCHA v2/v3, Cloudflare Turnstile, and image CAPTCHA solving with production-ready Kotlin code using both coroutines and blocking approaches.


Why Kotlin for CAPTCHA Automation

  • Coroutines — structured concurrency for parallel CAPTCHA solving without callback hell
  • Null safety — compiler prevents NPE crashes in API response handling
  • JVM ecosystem — access to all Java libraries (OkHttp, Apache HttpClient)
  • Multiplatform — Ktor client works on JVM, Android, iOS, and native targets
  • Data classes — clean API response modeling with minimal boilerplate

Prerequisites

Gradle (Kotlin DSL)

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")

    // Alternative: Ktor client
    implementation("io.ktor:ktor-client-core:2.3.12")
    implementation("io.ktor:ktor-client-cio:2.3.12")
    implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
}

Maven

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>1.8.1</version>
</dependency>

API Data Models

import kotlinx.serialization.Serializable

@Serializable
data class ApiResponse(
    val status: Int,
    val request: String
)

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

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

Method 1: OkHttp (Blocking)

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import kotlinx.serialization.json.Json

class CaptchaSolver(private val apiKey: String) {
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .build()
    private val json = Json { ignoreUnknownKeys = true }
    private val baseUrl = "https://ocr.captchaai.com"

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

    private fun submit(task: CaptchaTask): String {
        val formBody = FormBody.Builder()
            .add("key", apiKey)
            .add("json", "1")

        when (task) {
            is CaptchaTask.RecaptchaV2 -> formBody
                .add("method", "userrecaptcha")
                .add("googlekey", task.sitekey)
                .add("pageurl", task.pageUrl)
            is CaptchaTask.RecaptchaV3 -> formBody
                .add("method", "userrecaptcha")
                .add("googlekey", task.sitekey)
                .add("pageurl", task.pageUrl)
                .add("version", "v3")
                .add("action", task.action)
                .add("min_score", task.minScore.toString())
            is CaptchaTask.Turnstile -> formBody
                .add("method", "turnstile")
                .add("key", task.sitekey)
                .add("pageurl", task.pageUrl)
            is CaptchaTask.ImageBase64 -> formBody
                .add("method", "base64")
                .add("body", task.body)
        }

        val request = Request.Builder()
            .url("$baseUrl/in.php")
            .post(formBody.build())
            .build()

        val response = client.newCall(request).execute()
        val body = response.body?.string() ?: throw CaptchaException("Empty response")
        val parsed = json.decodeFromString<ApiResponse>(body)

        if (parsed.status != 1) throw CaptchaException("Submit failed: ${parsed.request}")
        return parsed.request
    }

    private fun poll(
        taskId: String,
        maxWaitMs: Long = 300_000,
        intervalMs: Long = 5_000
    ): String {
        val deadline = System.currentTimeMillis() + maxWaitMs

        while (System.currentTimeMillis() < deadline) {
            Thread.sleep(intervalMs)

            val url = HttpUrl.Builder()
                .scheme("https")
                .host("ocr.captchaai.com")
                .addPathSegment("res.php")
                .addQueryParameter("key", apiKey)
                .addQueryParameter("action", "get")
                .addQueryParameter("id", taskId)
                .addQueryParameter("json", "1")
                .build()

            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            val body = response.body?.string() ?: continue
            val parsed = json.decodeFromString<ApiResponse>(body)

            if (parsed.request == "CAPCHA_NOT_READY") continue
            if (parsed.status != 1) throw CaptchaException("Solve failed: ${parsed.request}")
            return parsed.request
        }

        throw CaptchaException("Timeout waiting for solution")
    }

    fun checkBalance(): Double {
        val url = HttpUrl.Builder()
            .scheme("https")
            .host("ocr.captchaai.com")
            .addPathSegment("res.php")
            .addQueryParameter("key", apiKey)
            .addQueryParameter("action", "getbalance")
            .addQueryParameter("json", "1")
            .build()

        val request = Request.Builder().url(url).build()
        val response = client.newCall(request).execute()
        val body = response.body?.string() ?: throw CaptchaException("Empty response")
        val parsed = json.decodeFromString<ApiResponse>(body)
        return parsed.request.toDouble()
    }
}

// Usage
fun main() {
    val solver = CaptchaSolver("YOUR_API_KEY")

    val balance = solver.checkBalance()
    println("Balance: $$balance")

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

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json

class AsyncCaptchaSolver(private val apiKey: String) {
    private val json = Json { ignoreUnknownKeys = true }
    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(json)
        }
        engine {
            requestTimeout = 30_000
        }
    }
    private val baseUrl = "https://ocr.captchaai.com"

    suspend fun solve(task: CaptchaTask): String {
        val taskId = submit(task)
        return poll(taskId)
    }

    private suspend fun submit(task: CaptchaTask): String {
        val response = client.submitForm(
            url = "$baseUrl/in.php",
            formParameters = parameters {
                append("key", apiKey)
                append("json", "1")
                when (task) {
                    is CaptchaTask.RecaptchaV2 -> {
                        append("method", "userrecaptcha")
                        append("googlekey", task.sitekey)
                        append("pageurl", task.pageUrl)
                    }
                    is CaptchaTask.RecaptchaV3 -> {
                        append("method", "userrecaptcha")
                        append("googlekey", task.sitekey)
                        append("pageurl", task.pageUrl)
                        append("version", "v3")
                        append("action", task.action)
                        append("min_score", task.minScore.toString())
                    }
                    is CaptchaTask.Turnstile -> {
                        append("method", "turnstile")
                        append("key", task.sitekey)
                        append("pageurl", task.pageUrl)
                    }
                    is CaptchaTask.ImageBase64 -> {
                        append("method", "base64")
                        append("body", task.body)
                    }
                }
            }
        )

        val parsed = json.decodeFromString<ApiResponse>(response.bodyAsText())
        if (parsed.status != 1) throw CaptchaException("Submit: ${parsed.request}")
        return parsed.request
    }

    private suspend fun poll(taskId: String, maxWaitMs: Long = 300_000): String {
        val deadline = System.currentTimeMillis() + maxWaitMs

        while (System.currentTimeMillis() < deadline) {
            delay(5_000)

            val response = client.get("$baseUrl/res.php") {
                parameter("key", apiKey)
                parameter("action", "get")
                parameter("id", taskId)
                parameter("json", "1")
            }

            val parsed = json.decodeFromString<ApiResponse>(response.bodyAsText())
            if (parsed.request == "CAPCHA_NOT_READY") continue
            if (parsed.status != 1) throw CaptchaException("Solve: ${parsed.request}")
            return parsed.request
        }

        throw CaptchaException("Timeout")
    }

    fun close() = client.close()
}

// Usage
fun main() = runBlocking {
    val solver = AsyncCaptchaSolver("YOUR_API_KEY")

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

Concurrent Solving with Coroutines

import kotlinx.coroutines.*

suspend fun solveBatch(
    solver: AsyncCaptchaSolver,
    tasks: List<CaptchaTask>
): List<Result<String>> = coroutineScope {
    tasks.map { task ->
        async {
            runCatching { solver.solve(task) }
        }
    }.awaitAll()
}

fun main() = runBlocking {
    val solver = AsyncCaptchaSolver("YOUR_API_KEY")

    val tasks = listOf(
        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 = solveBatch(solver, tasks)

    results.forEachIndexed { i, result ->
        result.fold(
            onSuccess = { println("Task $i: ${it.take(50)}...") },
            onFailure = { println("Task $i failed: ${it.message}") }
        )
    }

    solver.close()
}

Error Handling with Retry

suspend fun solveWithRetry(
    solver: AsyncCaptchaSolver,
    task: CaptchaTask,
    maxRetries: Int = 3
): String {
    val retryableErrors = setOf(
        "ERROR_NO_SLOT_AVAILABLE",
        "ERROR_CAPTCHA_UNSOLVABLE"
    )

    var lastException: Exception? = null

    repeat(maxRetries + 1) { attempt ->
        if (attempt > 0) {
            val delayMs = (1000L * Math.pow(2.0, attempt.toDouble())).toLong()
            println("Retry $attempt/$maxRetries after ${delayMs}ms")
            delay(delayMs)
        }

        try {
            return solver.solve(task)
        } catch (e: CaptchaException) {
            lastException = e
            val isRetryable = retryableErrors.any { e.message?.contains(it) == true }
            if (!isRetryable) throw e
        }
    }

    throw lastException ?: CaptchaException("Max retries exceeded")
}

Android Integration

// In a ViewModel
class CaptchaViewModel : ViewModel() {
    private val solver = AsyncCaptchaSolver("YOUR_API_KEY")

    private val _token = MutableLiveData<String>()
    val token: LiveData<String> = _token

    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error

    fun solveCaptcha(sitekey: String, pageUrl: String) {
        viewModelScope.launch {
            try {
                val result = solver.solve(
                    CaptchaTask.RecaptchaV2(sitekey, pageUrl)
                )
                _token.postValue(result)
            } catch (e: Exception) {
                _error.postValue(e.message)
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        solver.close()
    }
}

Spring Boot Integration

import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.*

@Service
class CaptchaService {
    private val solver = CaptchaSolver("YOUR_API_KEY")

    fun solveRecaptcha(sitekey: String, pageUrl: String): String {
        return solver.solve(CaptchaTask.RecaptchaV2(sitekey, pageUrl))
    }
}

@RestController
@RequestMapping("/api/captcha")
class CaptchaController(private val service: CaptchaService) {

    @PostMapping("/solve")
    fun solve(@RequestBody request: SolveRequest): Map<String, String> {
        val token = service.solveRecaptcha(request.sitekey, request.pageUrl)
        return mapOf("token" to token)
    }

    data class SolveRequest(val sitekey: String, val pageUrl: String)
}

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Invalid API key Verify key at dashboard
ERROR_ZERO_BALANCE No funds Top up account
UnresolvedReference Missing dependency Check Gradle/Maven config
SocketTimeoutException Network timeout Increase OkHttp/Ktor timeout
JsonDecodingException Unexpected API response Set ignoreUnknownKeys = true
Coroutine cancellation Scope cancelled early Use supervisorScope for batch

FAQ

Does CaptchaAI have a Kotlin SDK?

CaptchaAI provides a REST API. The examples here use standard Kotlin libraries (OkHttp, Ktor) for idiomatic integration.

Should I use OkHttp or Ktor?

Use OkHttp for JVM-only projects and when you want simple blocking calls. Use Ktor for coroutine-based code, multiplatform projects, or Android apps.

Can I use this on Android?

Yes. Use the Ktor client with coroutines and call solve() from a ViewModel's viewModelScope. The network calls run on background dispatchers automatically.

How do I handle concurrent solves?

Use coroutineScope + async as shown in the batch example. Kotlin coroutines handle thousands of concurrent operations efficiently.



Solve CAPTCHAs idiomatically in Kotlin — 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 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 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 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 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 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 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