API Tutorials

Solving CAPTCHAs with Swift and CaptchaAI API

Swift developers building iOS/macOS apps, server-side applications (Vapor), or command-line automation tools encounter CAPTCHAs in form submissions, API interactions, and web scraping. CaptchaAI's HTTP API integrates cleanly with Swift's native URLSession and modern async/await concurrency.

This guide covers reCAPTCHA v2/v3, Cloudflare Turnstile, and image CAPTCHA solving using Foundation's URLSession, plus Alamofire for projects already using it.


Why Swift for CAPTCHA Integration

  • Native async/await — structured concurrency built into the language (Swift 5.5+)
  • URLSession — no external dependencies needed for HTTP calls
  • Codable — automatic JSON encoding/decoding for API responses
  • Cross-platform — works on iOS, macOS, Linux (Swift on Server)
  • Type safety — enums and optionals catch API errors at compile time

Prerequisites

  • Swift 5.5+ (for async/await)
  • Xcode 13+ (for iOS/macOS) or Swift toolchain on Linux
  • CaptchaAI API key (get one here)

For Alamofire (optional):

// Package.swift
dependencies: [
    .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
]

Data Models

import Foundation

struct ApiResponse: Codable {
    let status: Int
    let request: String
}

enum CaptchaType {
    case recaptchaV2(sitekey: String, pageUrl: String)
    case recaptchaV3(sitekey: String, pageUrl: String, action: String, minScore: Double)
    case turnstile(sitekey: String, pageUrl: String)
    case imageBase64(body: String)
}

enum CaptchaError: Error, LocalizedError {
    case apiError(String)
    case timeout
    case invalidResponse
    case networkError(Error)

    var errorDescription: String? {
        switch self {
        case .apiError(let msg): return "API error: \(msg)"
        case .timeout: return "CAPTCHA solve timeout"
        case .invalidResponse: return "Invalid API response"
        case .networkError(let err): return "Network error: \(err.localizedDescription)"
        }
    }
}

Zero external dependencies — uses Foundation only.

class CaptchaSolver {
    private let apiKey: String
    private let baseURL = "https://ocr.captchaai.com"
    private let session: URLSession
    private let pollInterval: TimeInterval = 5.0
    private let maxWait: TimeInterval = 300.0

    init(apiKey: String) {
        self.apiKey = apiKey
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        self.session = URLSession(configuration: config)
    }

    func solve(_ captcha: CaptchaType) async throws -> String {
        let taskId = try await submit(captcha)
        return try await poll(taskId: taskId)
    }

    // MARK: - Submit

    private func submit(_ captcha: CaptchaType) async throws -> String {
        var params: [String: String] = [
            "key": apiKey,
            "json": "1"
        ]

        switch captcha {
        case .recaptchaV2(let sitekey, let pageUrl):
            params["method"] = "userrecaptcha"
            params["googlekey"] = sitekey
            params["pageurl"] = pageUrl

        case .recaptchaV3(let sitekey, let pageUrl, let action, let minScore):
            params["method"] = "userrecaptcha"
            params["googlekey"] = sitekey
            params["pageurl"] = pageUrl
            params["version"] = "v3"
            params["action"] = action
            params["min_score"] = String(minScore)

        case .turnstile(let sitekey, let pageUrl):
            params["method"] = "turnstile"
            params["key"] = sitekey
            params["pageurl"] = pageUrl

        case .imageBase64(let body):
            params["method"] = "base64"
            params["body"] = body
        }

        let url = URL(string: "\(baseURL)/in.php")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = params
            .map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)" }
            .joined(separator: "&")
            .data(using: .utf8)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

        let (data, _) = try await session.data(for: request)
        let response = try JSONDecoder().decode(ApiResponse.self, from: data)

        guard response.status == 1 else {
            throw CaptchaError.apiError(response.request)
        }

        return response.request
    }

    // MARK: - Poll

    private func poll(taskId: String) async throws -> String {
        let deadline = Date().addingTimeInterval(maxWait)

        while Date() < deadline {
            try await Task.sleep(nanoseconds: UInt64(pollInterval * 1_000_000_000))

            var components = URLComponents(string: "\(baseURL)/res.php")!
            components.queryItems = [
                URLQueryItem(name: "key", value: apiKey),
                URLQueryItem(name: "action", value: "get"),
                URLQueryItem(name: "id", value: taskId),
                URLQueryItem(name: "json", value: "1")
            ]

            let (data, _) = try await session.data(from: components.url!)
            let response = try JSONDecoder().decode(ApiResponse.self, from: data)

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

            guard response.status == 1 else {
                throw CaptchaError.apiError(response.request)
            }

            return response.request
        }

        throw CaptchaError.timeout
    }

    // MARK: - Balance

    func checkBalance() async throws -> Double {
        var components = URLComponents(string: "\(baseURL)/res.php")!
        components.queryItems = [
            URLQueryItem(name: "key", value: apiKey),
            URLQueryItem(name: "action", value: "getbalance"),
            URLQueryItem(name: "json", value: "1")
        ]

        let (data, _) = try await session.data(from: components.url!)
        let response = try JSONDecoder().decode(ApiResponse.self, from: data)

        guard let balance = Double(response.request) else {
            throw CaptchaError.invalidResponse
        }

        return balance
    }
}

Usage

@main
struct CaptchaApp {
    static func main() async throws {
        let solver = CaptchaSolver(apiKey: "YOUR_API_KEY")

        // Check balance
        let balance = try await solver.checkBalance()
        print("Balance: $\(String(format: "%.2f", balance))")

        // Solve reCAPTCHA v2
        let token = try await solver.solve(
            .recaptchaV2(
                sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
                pageUrl: "https://example.com/login"
            )
        )
        print("Token: \(String(token.prefix(50)))...")

        // Solve Turnstile
        let turnstileToken = try await solver.solve(
            .turnstile(
                sitekey: "0x4AAAAAAAB5...",
                pageUrl: "https://example.com/form"
            )
        )
        print("Turnstile: \(String(turnstileToken.prefix(50)))...")
    }
}

Image CAPTCHA Solving

func solveImageCaptcha(solver: CaptchaSolver, imagePath: String) async throws -> String {
    let imageData = try Data(contentsOf: URL(fileURLWithPath: imagePath))
    let base64 = imageData.base64EncodedString()
    return try await solver.solve(.imageBase64(body: base64))
}

// From UIImage (iOS)
func solveFromUIImage(solver: CaptchaSolver, image: UIImage) async throws -> String {
    guard let data = image.pngData() else {
        throw CaptchaError.invalidResponse
    }
    let base64 = data.base64EncodedString()
    return try await solver.solve(.imageBase64(body: base64))
}

Concurrent Solving with TaskGroup

func solveBatch(
    solver: CaptchaSolver,
    tasks: [CaptchaType]
) async -> [Result<String, Error>] {
    await withTaskGroup(of: (Int, Result<String, Error>).self) { group in
        for (index, task) in tasks.enumerated() {
            group.addTask {
                do {
                    let token = try await solver.solve(task)
                    return (index, .success(token))
                } catch {
                    return (index, .failure(error))
                }
            }
        }

        var results = Array<Result<String, Error>>(repeating: .failure(CaptchaError.timeout), count: tasks.count)
        for await (index, result) in group {
            results[index] = result
        }
        return results
    }
}

// Usage
let tasks: [CaptchaType] = [
    .recaptchaV2(sitekey: "KEY_A", pageUrl: "https://site-a.com"),
    .turnstile(sitekey: "KEY_B", pageUrl: "https://site-b.com"),
    .recaptchaV2(sitekey: "KEY_C", pageUrl: "https://site-c.com"),
]

let results = await solveBatch(solver: solver, tasks: tasks)
for (i, result) in results.enumerated() {
    switch result {
    case .success(let token):
        print("Task \(i): \(String(token.prefix(50)))...")
    case .failure(let error):
        print("Task \(i) failed: \(error)")
    }
}

Error Handling with Retry

func solveWithRetry(
    solver: CaptchaSolver,
    captcha: CaptchaType,
    maxRetries: Int = 3
) async throws -> String {
    let retryableErrors = ["ERROR_NO_SLOT_AVAILABLE", "ERROR_CAPTCHA_UNSOLVABLE"]

    for attempt in 0...maxRetries {
        if attempt > 0 {
            let delay = pow(2.0, Double(attempt)) + Double.random(in: 0...2)
            print("Retry \(attempt)/\(maxRetries) after \(delay)s")
            try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
        }

        do {
            return try await solver.solve(captcha)
        } catch CaptchaError.apiError(let msg) where retryableErrors.contains(where: { msg.contains($0) }) {
            if attempt == maxRetries { throw CaptchaError.apiError(msg) }
            continue
        } catch CaptchaError.timeout where attempt < maxRetries {
            continue
        }
    }

    throw CaptchaError.timeout
}

iOS SwiftUI Integration

import SwiftUI

@MainActor
class CaptchaViewModel: ObservableObject {
    @Published var token: String?
    @Published var error: String?
    @Published var isLoading = false

    private let solver = CaptchaSolver(apiKey: "YOUR_API_KEY")

    func solveCaptcha(sitekey: String, pageUrl: String) {
        isLoading = true
        token = nil
        error = nil

        Task {
            do {
                let result = try await solver.solve(
                    .recaptchaV2(sitekey: sitekey, pageUrl: pageUrl)
                )
                token = result
            } catch {
                self.error = error.localizedDescription
            }
            isLoading = false
        }
    }
}

struct CaptchaView: View {
    @StateObject private var viewModel = CaptchaViewModel()

    var body: some View {
        VStack(spacing: 16) {
            Button("Solve CAPTCHA") {
                viewModel.solveCaptcha(
                    sitekey: "SITEKEY",
                    pageUrl: "https://example.com"
                )
            }
            .disabled(viewModel.isLoading)

            if viewModel.isLoading {
                ProgressView("Solving...")
            }

            if let token = viewModel.token {
                Text("Solved!")
                    .foregroundColor(.green)
                Text(String(token.prefix(50)) + "...")
                    .font(.caption)
            }

            if let error = viewModel.error {
                Text(error)
                    .foregroundColor(.red)
            }
        }
        .padding()
    }
}

Vapor (Server-Side Swift) Integration

import Vapor

struct SolveRequest: Content {
    let sitekey: String
    let pageUrl: String
    let type: String?
}

struct SolveResponse: Content {
    let token: String
}

func routes(_ app: Application) throws {
    let solver = CaptchaSolver(apiKey: Environment.get("CAPTCHAAI_KEY") ?? "")

    app.post("api", "captcha", "solve") { req async throws -> SolveResponse in
        let body = try req.content.decode(SolveRequest.self)

        let captchaType: CaptchaType
        switch body.type {
        case "turnstile":
            captchaType = .turnstile(sitekey: body.sitekey, pageUrl: body.pageUrl)
        default:
            captchaType = .recaptchaV2(sitekey: body.sitekey, pageUrl: body.pageUrl)
        }

        let token = try await solver.solve(captchaType)
        return SolveResponse(token: token)
    }
}

Submitting Solved Tokens

func submitFormWithToken(
    url: String,
    token: String,
    formData: [String: String]
) async throws -> (Data, URLResponse) {
    var params = formData
    params["g-recaptcha-response"] = token

    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    request.httpBody = params
        .map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)" }
        .joined(separator: "&")
        .data(using: .utf8)
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

    return try await URLSession.shared.data(for: request)
}

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Invalid API key Verify key at dashboard
ERROR_ZERO_BALANCE No funds Top up account
NSURLErrorDomain Network issue Check connectivity, App Transport Security
DecodingError Unexpected JSON Enable ignoreUnknownKeys or check response
CancellationError Task cancelled Ensure Task isn't cancelled before completion
ATS blocked HTTP iOS blocks non-HTTPS CaptchaAI uses HTTPS — no ATS issue

FAQ

Does CaptchaAI have a Swift package?

CaptchaAI provides a REST API that works with URLSession (built into Swift). No external package needed.

Can I use this on iOS?

Yes. Use the async/await solver from a SwiftUI ViewModel or UIKit ViewController. Network calls run on background threads automatically.

Which Swift version do I need?

Swift 5.5+ for async/await. For older Swift, use completion handler-based URLSession calls.

Does this work with server-side Swift (Vapor)?

Yes. The solver uses Foundation's URLSession which works on Linux. For Vapor projects, you can also use AsyncHTTPClient.



Add CAPTCHA solving to your Swift apps — get your API key and integrate in minutes.

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...

Python Automation Cloudflare Turnstile
Apr 06, 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 Cloudflare Turnstile reCAPTCHA v2
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 Cloudflare Turnstile reCAPTCHA v2
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 Cloudflare Turnstile reCAPTCHA v2
Mar 11, 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 Cloudflare Turnstile reCAPTCHA v2
Mar 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...

Python Automation Cloudflare Turnstile
Feb 27, 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 Cloudflare Turnstile reCAPTCHA v2
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 Cloudflare Turnstile reCAPTCHA v2
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 Cloudflare Turnstile reCAPTCHA v2
Feb 05, 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,...

Python Automation Cloudflare Turnstile
Jan 31, 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...

Python Automation reCAPTCHA v2
Apr 08, 2026
API Tutorials Custom CAPTCHA Types: Submitting Unusual Challenges to CaptchaAI
How to submit non-standard and custom CAPTCHA types to Captcha AI — drag-and-drop, slider, puzzle, audio, and custom interactive challenges.

How to submit non-standard and custom CAPTCHA types to Captcha AI — drag-and-drop, slider, puzzle, audio, and...

Python Web Scraping Image OCR
Feb 07, 2026