captchaai

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 16 Imported by: 0

README

CaptchaAI Go SDK

Go client for the CaptchaAI captcha-solving API.

Call one method per captcha type; the SDK handles HTTP, polling, retries, and error mapping. Every solve returns a single SolveResult.


Table of Contents


Requirements

  • Go 1.22+
  • Zero third-party dependencies (net/http and the standard library only)

Installation

go get github.com/CaptchaAI/captchaai-go

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/CaptchaAI/captchaai-go"
)

func main() {
	ctx := context.Background()

	client, err := captchaai.New(ctx, "YOUR_32_CHAR_API_KEY")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	result, err := client.RecaptchaV2(ctx,
		"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
		"https://google.com/recaptcha/api2/demo",
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result.String()) // g-recaptcha-response token
}

New validates the API key and reads the account thread cap via one threadsinfo call. A bad key fails immediately with ErrInvalidKey.

Every method accepts context.Context first for cancellation and deadlines. There is no separate async client type — use goroutines and context when you need concurrent solves (see Other methods).


Configuration

Create a client with your API key:

client, err := captchaai.New(ctx, "YOUR_32_CHAR_API_KEY",
	captchaai.WithProxy("user:pass@host:port", "HTTP"),
	captchaai.WithAutoRetry(true),
	captchaai.WithMaxRetries(5),
	captchaai.WithBaseURL("https://ocr.captchaai.com"),
	captchaai.WithThreadBusyTimeout(120),
	captchaai.WithMaxPollSeconds(180),
)
Option Default Description
apiKey (constructor arg) (required) 32-character CaptchaAI API key
WithProxy(proxy, proxytype) none "user:pass@host:port" applied to every solve
WithAutoRetry(bool) true Retry transient errors with exponential backoff
WithMaxRetries(n) 5 0 disables retries; n > 0 enables up to n submit attempts (overrides WithAutoRetry when set last)
WithBaseURL(url) https://ocr.captchaai.com API host for in.php / res.php
WithThreadBusyTimeout(seconds) 120 Max seconds retrying when all account threads are busy
WithMaxPollSeconds(seconds) 180 Poll deadline before ErrTimeout
WithHTTPClient(*http.Client) internal Custom HTTP client for tests or custom transport

Call client.Close() when finished to release idle HTTP connections.

Retry behaviour
Setting Behaviour
WithAutoRetry(true) (default) Retry transient submit/proxy errors and thread-busy conditions with backoff
WithAutoRetry(false) Raise immediately on transient errors; also fast-fails when the local in-flight count hits the thread cap
WithMaxRetries(N) (N > 0) Like WithAutoRetry(true), capped at N submit attempts
WithMaxRetries(0) Like WithAutoRetry(false)

Fatal errors (ErrInvalidKey, ErrValidation, ErrUnsolvable, ErrNoThreads, and similar) always return immediately.


Solve result

Every solve method returns a SolveResult:

result.TaskID    // string — task ID from submit (always present)
result.Solution  // any — string token, []any cell indices, or map[string]any structured answer
result.UserAgent // string — set for Enterprise / Cloudflare Challenge / CaptchaFox when the API returns it
result.Raw       // map[string]any — full API response (debugging)
result.RawText    // string — unparsed solution string before JSON parsing (structured types)

result.String()  // token string when Solution is a string; otherwise fmt.Sprintf("%v", Solution)

Reuse result.UserAgent on the target site when it is present (reCAPTCHA Enterprise, Cloudflare Challenge, CaptchaFox).

Captcha type Typical Solution type UserAgent
Normal, reCAPTCHA v2/v3 (standard/invisible), Turnstile, Friendly Captcha string empty
reCAPTCHA v2/v3 Enterprise, Cloudflare Challenge, CaptchaFox string set when API returns it
Grid, BLS []any (parsed JSON) empty
GeeTest, Lemin map[string]any (parsed JSON) empty

Normal captcha

Solve a text/image captcha. image may be a file path, URL, data-URI, raw base64 string, or []byte.

result, err := client.Normal(ctx, "captcha.png",
	captchaai.WithNumeric(1),       // 0=any, 1=digits, 2=letters, 3=digits+letters, 4=no digits
	captchaai.WithMinLen(4),
	captchaai.WithMaxLen(6),
	captchaai.WithPhrase(0),        // 0=single word, 1=multi-word
	captchaai.WithCaseSensitive(0), // 0=insensitive, 1=case-sensitive
	captchaai.WithLang("en"),
	captchaai.WithInstructions("Type the characters you see"),
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution)

Call options: WithNumeric, WithMinLen, WithMaxLen, WithPhrase, WithCaseSensitive, WithLang, WithInstructions, WithCallProxy.


Grid captcha

Solve a tile-selection captcha. gridSize must be "3x3" or "4x4". Solution is a list of cell indices.

result, err := client.Grid(ctx,
	"grid.png",
	"select all traffic lights",
	"3x3",
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution)

Call options: WithCallProxy.


BLS captcha

Solve a BLS multi-image captcha. Pass exactly 9 images (path, URL, base64, or []byte each). Solution is a list of cell indices.

images := make([]any, 9)
for i := range images {
	images[i] = fmt.Sprintf("img%d.png", i)
}

result, err := client.BLS(ctx, images, "YOUR_INSTRUCTIONS")
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution)

Call options: WithCallProxy.


reCAPTCHA v2

Solve reCAPTCHA v2 (standard, invisible, or enterprise). Pass sitekey and page url. Enterprise results may include UserAgent.

// Standard
result, err := client.RecaptchaV2(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.String())

// Invisible
result, err = client.RecaptchaV2(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithInvisible(),
)

// Enterprise (optional action; result may include UserAgent)
result, err = client.RecaptchaV2(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithEnterprise(),
	captchaai.WithAction("login"),
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution, result.UserAgent)

Call options: WithInvisible, WithEnterprise, WithAction, WithCookies, WithUserAgent, WithCallProxy.


reCAPTCHA v3

Solve reCAPTCHA v3 (standard or enterprise). WithAction is required — the API registry rejects submits without an action.

result, err := client.RecaptchaV3(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithAction("login"),
	captchaai.WithMinScore(0.3),
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.String())

// Enterprise
result, err = client.RecaptchaV3(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithAction("login"),
	captchaai.WithEnterprise(),
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution, result.UserAgent)

Call options: WithEnterprise, WithAction, WithMinScore, WithCookies, WithUserAgent, WithCallProxy.


Cloudflare Turnstile

Solve a Cloudflare Turnstile widget.

result, err := client.Turnstile(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.String())

Call options: WithCookies, WithUserAgent, WithCallProxy.


Cloudflare Challenge

Solve a Cloudflare interstitial challenge page. A proxy is mandatory (client-level or per-call). The result includes UserAgent to reuse on the target site.

client, err := captchaai.New(ctx, "YOUR_32_CHAR_API_KEY",
	captchaai.WithProxy("user:pass@host:port", "HTTP"),
)
if err != nil {
	log.Fatal(err)
}
defer client.Close()

result, err := client.CloudflareChallenge(ctx, "https://example.com")
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution, result.UserAgent)

Call options: WithCookies, WithUserAgent, WithCallProxy.


GeeTest

Solve GeeTest v3. Solution is a map[string]any with challenge, validate, and seccode.

result, err := client.Geetest(ctx,
	"YOUR_GT",
	"YOUR_CHALLENGE",
	"https://example.com",
)
if err != nil {
	log.Fatal(err)
}

m := result.Solution.(map[string]any)
fmt.Println(m["challenge"])
fmt.Println(m["validate"])
fmt.Println(m["seccode"])

Call options: WithCookies, WithUserAgent, WithCallProxy.


CaptchaFox

Solve a CaptchaFox slider challenge. A proxy is mandatory. Reuse result.UserAgent when submitting the token.

client, err := captchaai.New(ctx, "YOUR_32_CHAR_API_KEY",
	captchaai.WithProxy("user:pass@host:port", "HTTP"),
)
if err != nil {
	log.Fatal(err)
}
defer client.Close()

result, err := client.Captchafox(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution, result.UserAgent)

Call options: WithCookies, WithUserAgent, WithCallProxy.


Friendly Captcha

Solve a Friendly Captcha proof-of-work challenge. No proxy is required.

result, err := client.FriendlyCaptcha(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithVersion("v1"), // optional
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.String())

Call options: WithVersion, WithCallProxy.


Lemin

Solve a Lemin puzzle. Solution is a map[string]any with answer and challenge_uuid.

result, err := client.Lemin(ctx,
	"YOUR_CAPTCHA_ID",
	"lemin-cropped-captcha",
	"https://example.com",
	captchaai.WithAPIServer("api.leminnow.com"), // optional
)
if err != nil {
	log.Fatal(err)
}

m := result.Solution.(map[string]any)
fmt.Println(m["answer"])
fmt.Println(m["challenge_uuid"])

Call options: WithAPIServer, WithCallProxy.


Other methods

Thread usage

CaptchaAI is thread-based. Check current usage:

usage, err := client.ThreadsInfo(ctx)
if err != nil {
	log.Fatal(err)
}
// usage.Threads, usage.WorkingThreads
Manual submit / fetch

Submit now and poll later (params use API names, e.g. googlekey, pageurl):

taskID, err := client.Send(ctx, "recaptcha_v2", map[string]any{
	"googlekey": "YOUR_SITEKEY",
	"pageurl":   "https://example.com",
})
if err != nil {
	log.Fatal(err)
}

result, err := client.GetResult(ctx, "recaptcha_v2", taskID)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Solution)
Close
client.Close()
Concurrency

This SDK has no separate async client (unlike the Python AsyncCaptchaAI). All methods are blocking calls that respect context.Context for cancellation and deadlines.

For concurrent solves, launch goroutines and pass a shared or derived context:

var wg sync.WaitGroup
for _, job := range jobs {
	wg.Add(1)
	go func(sitekey, pageURL string) {
		defer wg.Done()
		result, err := client.Turnstile(ctx, sitekey, pageURL)
		// handle result / err
	}(job.Sitekey, job.URL)
}
wg.Wait()

When WithAutoRetry(false) or WithMaxRetries(0), the client tracks in-flight solves locally and returns ErrThreadLimit when the account thread cap is reached. With auto-retry enabled (default), the SDK retries thread-busy conditions server-side up to WithThreadBusyTimeout.


Error handling

All SDK errors are *captchaai.Error with a Kind field. Use errors.As to inspect the kind, or errors.Is against sentinel values:

result, err := client.RecaptchaV2(ctx, sitekey, pageURL)
if err != nil {
	var sdkErr *captchaai.Error
	if errors.As(err, &sdkErr) {
		switch sdkErr.Kind {
		case captchaai.KindInvalidKey:
			// wrong-length key, or API rejected the key
		case captchaai.KindValidation:
			// missing/invalid params, bad image input, missing required proxy, etc.
		case captchaai.KindProxy:
			// bad proxy or proxy connection failed
		case captchaai.KindThreadLimit:
			// all account threads busy (and retries skipped or exhausted)
		case captchaai.KindNoThreads:
			// account expired or has no active plan
		case captchaai.KindUnsolvable:
			// service could not solve the captcha
		case captchaai.KindNetwork:
			// could not reach the API
		case captchaai.KindTimeout:
			// poll deadline exceeded before a result was ready
		case captchaai.KindAPI:
			// unexpected / malformed API response
		}
	}

	// Or use sentinels with errors.Is:
	if errors.Is(err, captchaai.ErrThreadLimit) {
		// all threads busy
	}
}
Sentinel When raised
ErrInvalidKey API returns ERROR_WRONG_USER_KEY or ERROR_KEY_DOES_NOT_EXIST
ErrValidation Invalid key length at New; missing/invalid parameters; bad image input; proxy required but missing; unknown captcha type
ErrProxy Bad proxy (ERROR_BAD_PROXY) or proxy connection failed
ErrThreadLimit All threads busy and retries skipped or exhausted; local in-flight cap reached when auto-retry is disabled
ErrNoThreads Account expired or has no active plan (ERROR_ZERO_BALANCE with spare thread capacity)
ErrUnsolvable Captcha could not be solved (ERROR_CAPTCHA_UNSOLVABLE)
ErrAPI Unexpected or malformed API response
ErrNetwork HTTP transport failure contacting the API
ErrTimeout Poll deadline exceeded (WithMaxPollSeconds) before a result was ready

Context cancellation and deadlines return the standard library error from context (context.Canceled, context.DeadlineExceeded) — not wrapped as *captchaai.Error.


Proxies

Proxies are supported at client level and per call. Format: "user:pass@host:port". proxytype is required whenever a proxy is set (HTTP, HTTPS, SOCKS4, or SOCKS5).

// Client-level (every solve)
client, err := captchaai.New(ctx, "YOUR_32_CHAR_API_KEY",
	captchaai.WithProxy("user:pass@host:port", "HTTP"),
)

// Per-call override (does not mutate client config)
result, err := client.RecaptchaV2(ctx,
	"YOUR_SITEKEY",
	"https://example.com",
	captchaai.WithCallProxy("user:pass@other-host:port", "SOCKS5"),
)

Proxy is mandatory (enforced at submit time) for:

  • CloudflareChallenge
  • Captchafox

License

This project is licensed under the MIT License.

Copyright (c) 2026 Dev@Captchaai

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidKey  = &Error{Kind: KindInvalidKey}
	ErrValidation  = &Error{Kind: KindValidation}
	ErrProxy       = &Error{Kind: KindProxy}
	ErrThreadLimit = &Error{Kind: KindThreadLimit}
	ErrNoThreads   = &Error{Kind: KindNoThreads}
	ErrUnsolvable  = &Error{Kind: KindUnsolvable}
	ErrAPI         = &Error{Kind: KindAPI}
	ErrNetwork     = &Error{Kind: KindNetwork}
	ErrTimeout     = &Error{Kind: KindTimeout}
)

Functions

This section is empty.

Types

type CallOption

type CallOption func(*callOptions)

func WithAPIServer

func WithAPIServer(apiServer string) CallOption

func WithAction

func WithAction(action string) CallOption

func WithCallProxy

func WithCallProxy(proxy, proxytype string) CallOption

func WithCaseSensitive

func WithCaseSensitive(caseSensitive int) CallOption

func WithCookies

func WithCookies(cookies string) CallOption

func WithEnterprise

func WithEnterprise() CallOption

func WithInstructions

func WithInstructions(instructions string) CallOption

func WithInvisible

func WithInvisible() CallOption

func WithLang

func WithLang(lang string) CallOption

func WithMaxLen

func WithMaxLen(maxLen int) CallOption

func WithMinLen

func WithMinLen(minLen int) CallOption

func WithMinScore

func WithMinScore(score float64) CallOption

func WithNumeric

func WithNumeric(numeric int) CallOption

func WithPhrase

func WithPhrase(phrase int) CallOption

func WithUserAgent

func WithUserAgent(userAgent string) CallOption

func WithVersion

func WithVersion(version string) CallOption

type Client

type Client struct {
	// contains filtered or unexported fields
}

func New

func New(ctx context.Context, apiKey string, opts ...Option) (*Client, error)

func (*Client) BLS

func (c *Client) BLS(ctx context.Context, images []any, instructions string, opts ...CallOption) (SolveResult, error)

func (*Client) Captchafox

func (c *Client) Captchafox(ctx context.Context, sitekey, url string, opts ...CallOption) (SolveResult, error)

func (*Client) Close

func (c *Client) Close()

func (*Client) CloudflareChallenge

func (c *Client) CloudflareChallenge(ctx context.Context, url string, opts ...CallOption) (SolveResult, error)

func (*Client) FriendlyCaptcha

func (c *Client) FriendlyCaptcha(ctx context.Context, sitekey, url string, opts ...CallOption) (SolveResult, error)

func (*Client) Geetest

func (c *Client) Geetest(ctx context.Context, gt, challenge, url string, opts ...CallOption) (SolveResult, error)

func (*Client) GetResult

func (c *Client) GetResult(ctx context.Context, captchaType, taskID string) (SolveResult, error)

func (*Client) Grid

func (c *Client) Grid(ctx context.Context, image any, instructions, gridSize string, opts ...CallOption) (SolveResult, error)

func (*Client) Lemin

func (c *Client) Lemin(ctx context.Context, captchaID, divID, url string, opts ...CallOption) (SolveResult, error)

func (*Client) Normal

func (c *Client) Normal(ctx context.Context, image any, opts ...CallOption) (SolveResult, error)

func (*Client) RecaptchaV2

func (c *Client) RecaptchaV2(ctx context.Context, sitekey, url string, opts ...CallOption) (SolveResult, error)

func (*Client) RecaptchaV3

func (c *Client) RecaptchaV3(ctx context.Context, sitekey, url string, opts ...CallOption) (SolveResult, error)

func (*Client) Send

func (c *Client) Send(ctx context.Context, captchaType string, params map[string]any) (string, error)

func (*Client) ThreadsInfo

func (c *Client) ThreadsInfo(ctx context.Context) (ThreadUsage, error)

func (*Client) Turnstile

func (c *Client) Turnstile(ctx context.Context, sitekey, url string, opts ...CallOption) (SolveResult, error)

type Error

type Error struct {
	Kind    ErrorKind
	Message string
	Err     error
}

Error is the single error type every SDK call returns.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorKind

type ErrorKind int

ErrorKind classifies every failure the SDK can return. It mirrors the Python exception hierarchy as a single error type usable with errors.Is.

const (
	KindInvalidKey ErrorKind = iota
	KindValidation
	KindProxy
	KindThreadLimit
	KindNoThreads
	KindUnsolvable
	KindAPI
	KindNetwork
	KindTimeout
)

func (ErrorKind) String

func (k ErrorKind) String() string

type Option

type Option func(*config)

func WithAutoRetry

func WithAutoRetry(autoRetry bool) Option

func WithBaseURL

func WithBaseURL(baseURL string) Option

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

func WithMaxPollSeconds

func WithMaxPollSeconds(seconds float64) Option

func WithMaxRetries

func WithMaxRetries(maxRetries int) Option

func WithProxy

func WithProxy(proxy, proxytype string) Option

func WithThreadBusyTimeout

func WithThreadBusyTimeout(seconds float64) Option

type SolveResult

type SolveResult struct {
	TaskID    string
	Solution  any
	UserAgent string
	Raw       map[string]any
	RawText   string
}

SolveResult is the single return type for every solve.

func (SolveResult) String

func (r SolveResult) String() string

type ThreadUsage

type ThreadUsage struct {
	Threads        int
	WorkingThreads int
}

type Transport

type Transport interface {
	RequestRaw(ctx context.Context, method, rawURL string, data *orderedParams) UniformResponse
	CloseIdleConnections()
}

type UniformResponse

type UniformResponse struct {
	StatusCode            int
	Body                  map[string]any
	RawText               string
	TransportErrorType    string
	TransportErrorMessage string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL