API Tutorials

Building a Go Client Library for CaptchaAI API

Go's strong typing, built-in concurrency, and single-binary deployment make it a solid choice for automation systems. This guide builds a CaptchaAI client library that follows Go conventions — context.Context support, custom http.Client injection, and typed error values.

Package Structure

captchaai/
├── client.go       # Main client and solve logic
├── errors.go       # Error types
├── types.go        # Request/response structs
└── client_test.go  # Tests

Error Types

// errors.go
package captchaai

import "fmt"

// APIError represents a CaptchaAI API error response.
type APIError struct {
    Code    string
    Message string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("captchaai: %s (%s)", e.Message, e.Code)
}

// IsFatal returns true if this error should not be retried.
func (e *APIError) IsFatal() bool {
    switch e.Code {
    case "ERROR_WRONG_USER_KEY", "ERROR_KEY_DOES_NOT_EXIST",
        "ERROR_ZERO_BALANCE", "ERROR_IP_NOT_ALLOWED":
        return true
    }
    return false
}

// TimeoutError indicates the solve exceeded the configured timeout.
type TimeoutError struct {
    TaskID string
}

func (e *TimeoutError) Error() string {
    return fmt.Sprintf("captchaai: task %s timed out", e.TaskID)
}

Types

// types.go
package captchaai

import "time"

// ClientOption configures the CaptchaAI client.
type ClientOption func(*Client)

// WithPollInterval sets the polling interval between result checks.
func WithPollInterval(d time.Duration) ClientOption {
    return func(c *Client) { c.pollInterval = d }
}

// WithTimeout sets the maximum time to wait for a solution.
func WithTimeout(d time.Duration) ClientOption {
    return func(c *Client) { c.timeout = d }
}

// RecaptchaV2Params holds parameters for reCAPTCHA v2 solving.
type RecaptchaV2Params struct {
    SiteKey   string
    PageURL   string
    Invisible bool
    Cookies   string
}

// RecaptchaV3Params holds parameters for reCAPTCHA v3 solving.
type RecaptchaV3Params struct {
    SiteKey  string
    PageURL  string
    Action   string
    MinScore float64
}

// TurnstileParams holds parameters for Cloudflare Turnstile solving.
type TurnstileParams struct {
    SiteKey string
    PageURL string
    Action  string
    CData   string
}

// ImageParams holds parameters for image/OCR CAPTCHA solving.
type ImageParams struct {
    Base64Image   string
    CaseSensitive bool
    MinLength     int
    MaxLength     int
}

type submitResponse struct {
    Status  int    `json:"status"`
    Request string `json:"request"`
}

type pollResponse struct {
    Status  int    `json:"status"`
    Request string `json:"request"`
}

Client Implementation

// client.go
package captchaai

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "strconv"
    "time"
)

const (
    submitURL           = "https://ocr.captchaai.com/in.php"
    resultURL           = "https://ocr.captchaai.com/res.php"
    defaultPollInterval = 5 * time.Second
    defaultTimeout      = 180 * time.Second
)

// Client interacts with the CaptchaAI API.
type Client struct {
    apiKey       string
    httpClient   *http.Client
    pollInterval time.Duration
    timeout      time.Duration
}

// New creates a CaptchaAI client with the given API key and options.
func New(apiKey string, opts ...ClientOption) *Client {
    c := &Client{
        apiKey:       apiKey,
        httpClient:   http.DefaultClient,
        pollInterval: defaultPollInterval,
        timeout:      defaultTimeout,
    }
    for _, opt := range opts {
        opt(c)
    }
    return c
}

// WithHTTPClient sets a custom HTTP client (e.g., for proxy support).
func WithHTTPClient(hc *http.Client) ClientOption {
    return func(c *Client) { c.httpClient = hc }
}

func (c *Client) submit(ctx context.Context, params url.Values) (string, error) {
    params.Set("key", c.apiKey)
    params.Set("json", "1")

    req, err := http.NewRequestWithContext(ctx, http.MethodPost, submitURL, nil)
    if err != nil {
        return "", fmt.Errorf("captchaai: build request: %w", err)
    }
    req.URL.RawQuery = params.Encode()

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return "", fmt.Errorf("captchaai: submit: %w", err)
    }
    defer resp.Body.Close()

    var result submitResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return "", fmt.Errorf("captchaai: decode submit response: %w", err)
    }

    if result.Status != 1 {
        return "", &APIError{Code: result.Request, Message: "submit failed"}
    }

    return result.Request, nil
}

func (c *Client) poll(ctx context.Context, taskID string) (string, error) {
    deadline := time.After(c.timeout)

    for {
        select {
        case <-ctx.Done():
            return "", ctx.Err()
        case <-deadline:
            return "", &TimeoutError{TaskID: taskID}
        case <-time.After(c.pollInterval):
        }

        params := url.Values{
            "key":    {c.apiKey},
            "action": {"get"},
            "id":     {taskID},
            "json":   {"1"},
        }

        req, err := http.NewRequestWithContext(ctx, http.MethodGet, resultURL+"?"+params.Encode(), nil)
        if err != nil {
            return "", fmt.Errorf("captchaai: build poll request: %w", err)
        }

        resp, err := c.httpClient.Do(req)
        if err != nil {
            continue // Retry on network error
        }

        var result pollResponse
        if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
            resp.Body.Close()
            continue
        }
        resp.Body.Close()

        if result.Request == "CAPCHA_NOT_READY" {
            continue
        }

        if result.Status == 1 {
            return result.Request, nil
        }

        return "", &APIError{Code: result.Request, Message: "solve failed"}
    }
}

// SolveRecaptchaV2 solves a reCAPTCHA v2 challenge.
func (c *Client) SolveRecaptchaV2(ctx context.Context, p RecaptchaV2Params) (string, error) {
    params := url.Values{
        "method":    {"userrecaptcha"},
        "googlekey": {p.SiteKey},
        "pageurl":   {p.PageURL},
    }
    if p.Invisible {
        params.Set("invisible", "1")
    }
    if p.Cookies != "" {
        params.Set("cookies", p.Cookies)
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveRecaptchaV3 solves a reCAPTCHA v3 challenge.
func (c *Client) SolveRecaptchaV3(ctx context.Context, p RecaptchaV3Params) (string, error) {
    params := url.Values{
        "method":    {"userrecaptcha"},
        "version":   {"v3"},
        "googlekey": {p.SiteKey},
        "pageurl":   {p.PageURL},
    }
    if p.Action != "" {
        params.Set("action", p.Action)
    }
    if p.MinScore > 0 {
        params.Set("min_score", strconv.FormatFloat(p.MinScore, 'f', 1, 64))
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveTurnstile solves a Cloudflare Turnstile challenge.
func (c *Client) SolveTurnstile(ctx context.Context, p TurnstileParams) (string, error) {
    params := url.Values{
        "method":  {"turnstile"},
        "sitekey": {p.SiteKey},
        "pageurl": {p.PageURL},
    }
    if p.Action != "" {
        params.Set("action", p.Action)
    }
    if p.CData != "" {
        params.Set("data", p.CData)
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// SolveImage solves an image/text CAPTCHA from base64.
func (c *Client) SolveImage(ctx context.Context, p ImageParams) (string, error) {
    params := url.Values{
        "method": {"base64"},
        "body":   {p.Base64Image},
    }
    if p.CaseSensitive {
        params.Set("regsense", "1")
    }
    if p.MinLength > 0 {
        params.Set("min_len", strconv.Itoa(p.MinLength))
    }
    if p.MaxLength > 0 {
        params.Set("max_len", strconv.Itoa(p.MaxLength))
    }

    taskID, err := c.submit(ctx, params)
    if err != nil {
        return "", err
    }
    return c.poll(ctx, taskID)
}

// GetBalance returns the current account balance.
func (c *Client) GetBalance(ctx context.Context) (float64, error) {
    params := url.Values{
        "key":    {c.apiKey},
        "action": {"getbalance"},
        "json":   {"1"},
    }

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, resultURL+"?"+params.Encode(), nil)
    if err != nil {
        return 0, err
    }

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()

    var result pollResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return 0, err
    }

    return strconv.ParseFloat(result.Request, 64)
}

Usage

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "your-module/captchaai"
)

func main() {
    client := captchaai.New("YOUR_API_KEY",
        captchaai.WithTimeout(120*time.Second),
        captchaai.WithPollInterval(5*time.Second),
    )

    ctx := context.Background()

    // Check balance
    balance, err := client.GetBalance(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Balance: $%.2f\n", balance)

    // Solve reCAPTCHA v2
    token, err := client.SolveRecaptchaV2(ctx, captchaai.RecaptchaV2Params{
        SiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
        PageURL: "https://example.com/login",
    })
    if err != nil {
        var apiErr *captchaai.APIError
        if errors.As(err, &apiErr) && apiErr.IsFatal() {
            log.Fatalf("Fatal API error: %s", apiErr.Code)
        }
        log.Fatal(err)
    }
    fmt.Printf("Token: %s...\n", token[:40])

    // Solve with context timeout
    solveCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
    defer cancel()

    turnstileToken, err := client.SolveTurnstile(solveCtx, captchaai.TurnstileParams{
        SiteKey: "0x4AAAAAAADnPIDROrmt1Wwj",
        PageURL: "https://example.com/checkout",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Turnstile: %s...\n", turnstileToken[:40])
}

Troubleshooting

Issue Cause Fix
context deadline exceeded Solve took longer than context timeout Use longer context timeout or increase client WithTimeout
captchaai: submit failed (ERROR_ZERO_BALANCE) No funds Top up at CaptchaAI dashboard
Polls never complete Network issues or wrong API URL Check connectivity; verify URL constants
Compiler error on errors.As Missing import Add "errors" to imports
Custom HTTP client not used Forgot WithHTTPClient option Pass option in New(): captchaai.New(key, captchaai.WithHTTPClient(myClient))

FAQ

Why use context.Context instead of a simple timeout?

Context integrates with Go's standard cancellation pattern. If the parent HTTP handler or goroutine is cancelled, the CAPTCHA solve stops immediately — no orphaned polling loops consuming API credits.

How do I use this with a proxy?

Inject a custom http.Client with a proxy transport. This routes all SDK traffic through your proxy without modifying the library.

Should I use go install or vendoring?

For private projects, use go mod vendor. For reusable libraries, publish as a Go module with semantic versioning and let consumers import with go get.

Next Steps

Build your Go CAPTCHA client — get your CaptchaAI API key and start with the package above.

Related guides:

Discussions (0)

No comments yet.

Related Posts

Tutorials Solve CAPTCHAs with Go Using CaptchaAI
Step-by-step Go tutorial for solving re CAPTCHA, Turnstile, image CAPTCHAs and using the Captcha AI REST API.

Step-by-step Go tutorial for solving re CAPTCHA, Turnstile, image CAPTCHAs and more using the Captcha AI REST...

Automation All CAPTCHA Types Go
Mar 29, 2026
DevOps & Scaling Ansible Playbooks for CaptchaAI Worker Deployment
Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates, and health checks across your server fleet.

Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates...

Automation Python All CAPTCHA Types
Apr 07, 2026
DevOps & Scaling Blue-Green Deployment for CAPTCHA Solving Infrastructure
Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switching, and rollback strategies with Captcha AI.

Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switchin...

Automation Python All CAPTCHA Types
Apr 07, 2026
Troubleshooting CaptchaAI API Error Handling: Complete Decision Tree
Complete decision tree for every Captcha AI API error.

Complete decision tree for every Captcha AI API error. Learn which errors are retryable, which need parameter...

Automation Python All CAPTCHA Types
Mar 17, 2026
Tutorials Using Fiddler to Inspect CaptchaAI API Traffic
How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and responses — filters, breakpoints, and replay for tr...

How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and r...

Automation Python All CAPTCHA Types
Mar 05, 2026
Tutorials CAPTCHA Handling in Mobile Apps with Appium
Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web sitekeys, solve, and inject tokens on Android and i OS.

Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web View sitekeys, solve, and i...

Automation Python All CAPTCHA Types
Feb 13, 2026
Tutorials Streaming Batch Results: Processing CAPTCHA Solutions as They Arrive
Process CAPTCHA solutions the moment they arrive instead of waiting for tasks to complete — use async generators, event emitters, and callback patterns for stre...

Process CAPTCHA solutions the moment they arrive instead of waiting for all tasks to complete — use async gene...

Automation Python All CAPTCHA Types
Apr 07, 2026
Reference CaptchaAI CLI Tool: Command-Line CAPTCHA Solving and Testing
A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parameters, and integrate with shell scripts and CI/CD p...

A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parame...

Automation Python All CAPTCHA Types
Feb 26, 2026
DevOps & Scaling Auto-Scaling CAPTCHA Solving Workers
Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates.

Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates...

Automation Python All CAPTCHA Types
Mar 23, 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