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.
- Requirements
- Installation
- Quick start
- Configuration
- Solve result
- Normal captcha
- Grid captcha
- BLS captcha
- reCAPTCHA v2
- reCAPTCHA v3
- Cloudflare Turnstile
- Cloudflare Challenge
- GeeTest
- CaptchaFox
- Friendly Captcha
- Lemin
- Other methods
- Error handling
- Proxies
- License
- Go 1.22+
- Zero third-party dependencies (
net/httpand the standard library only)
go get github.com/CaptchaAI/captchaai-gopackage 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).
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.
| 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.
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 |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
CaptchaAI is thread-based. Check current usage:
usage, err := client.ThreadsInfo(ctx)
if err != nil {
log.Fatal(err)
}
// usage.Threads, usage.WorkingThreadsSubmit 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)client.Close()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.
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 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:
CloudflareChallengeCaptchafox
This project is licensed under the MIT License.
Copyright (c) 2026 Dev@Captchaai