Go Captcha Solver

Solve image CAPTCHAs in Go with pure stdlib — no external packages needed. FastCaptcha returns the answer in 0.3–0.7 seconds with 95% accuracy.

Go Quick Start

No go get required — pure net/http and encoding/json

Basic solve

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
)

const apiKey = "YOUR_API_KEY"

type APIResponse struct {
    Success        bool    `json:"success"`
    Text           string  `json:"text"`
    Confidence     float64 `json:"confidence"`
    ProcessingTime float64 `json:"processing_time"`
    Error          string  `json:"error"`
}

func SolveCaptcha(imagePath string) (string, error) {
    file, err := os.Open(imagePath)
    if err != nil {
        return "", err
    }
    defer file.Close()

    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)
    part, _ := writer.CreateFormFile("image", filepath.Base(imagePath))
    io.Copy(part, file)
    writer.Close()

    req, _ := http.NewRequest("POST",
        "https://fastcaptcha.org/api/v1/ocr/", &buf)
    req.Header.Set("X-API-Key", apiKey)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    var result APIResponse
    json.NewDecoder(resp.Body).Decode(&result)

    if !result.Success {
        return "", fmt.Errorf("API error: %s", result.Error)
    }
    return result.Text, nil
}

With retry & context timeout

import (
    "context"
    "math"
    "math/rand"
    "time"
)

func SolveWithRetry(imagePath string) (string, error) {
    maxRetries := 4
    for attempt := 0; attempt < maxRetries; attempt++ {
        text, err := SolveCaptcha(imagePath)
        if err == nil {
            return text, nil
        }

        // Only retry on transient errors
        errStr := err.Error()
        retriable := strings.Contains(errStr, "429") ||
            strings.Contains(errStr, "500") ||
            strings.Contains(errStr, "503")

        if !retriable || attempt == maxRetries-1 {
            return "", err
        }

        delay := math.Min(
            math.Pow(2, float64(attempt))+rand.Float64(),
            32,
        )
        fmt.Printf("Retrying in %.1fs (attempt %d)\n", delay, attempt+1)
        time.Sleep(time.Duration(delay * float64(time.Second)))
    }
    return "", fmt.Errorf("max retries exceeded")
}

// With context timeout (recommended)
func SolveWithTimeout(imagePath string) (string, error) {
    ctx, cancel := context.WithTimeout(
        context.Background(), 15*time.Second)
    defer cancel()

    req, _ := http.NewRequestWithContext(ctx, "POST", ...)
    // ... rest same as above
}

Concurrent solving with goroutines

func SolveBatch(imagePaths []string, concurrency int) []string {
    sem := make(chan struct{}, concurrency) // limit concurrency
    results := make([]string, len(imagePaths))
    var wg sync.WaitGroup

    for i, path := range imagePaths {
        wg.Add(1)
        go func(idx int, p string) {
            defer wg.Done()
            sem <- struct{}{}
            defer func() { <-sem }()

            text, err := SolveWithRetry(p)
            if err != nil {
                fmt.Printf("Error on %s: %v\n", p, err)
                return
            }
            results[idx] = text
        }(i, path)
    }
    wg.Wait()
    return results
}

// Usage:
// results := SolveBatch(images, 5) // 5 concurrent requests
0.3s avg

Sub-second responses on image CAPTCHAs

📦
Zero deps

Pure stdlib — no external packages

🔀
Goroutine-safe

Safe for concurrent use across goroutines