Tutorials

TypeScript + CaptchaAI: Type-Safe CAPTCHA Solving

TypeScript catches bugs at compile time. A fully typed CaptchaAI client prevents wrong parameters, invalid method names, and missing fields before your code runs.


Type definitions

// captcha-types.ts

export type CaptchaMethod =
  | "userrecaptcha"
  | "turnstile"
  | "geetest"
  | "geetest_v4"
  | "bls"
  | "base64"
  | "post"
  | "cloudflare_challenge";

export interface RecaptchaV2Params {
  method: "userrecaptcha";
  googlekey: string;
  pageurl: string;
  invisible?: "1";
  "data-s"?: string;
}

export interface RecaptchaV3Params {
  method: "userrecaptcha";
  googlekey: string;
  pageurl: string;
  version: "v3";
  action?: string;
  min_score?: string;
}

export interface TurnstileParams {
  method: "turnstile";
  sitekey: string;
  pageurl: string;
  action?: string;
  data?: string;
}

export interface GeeTestV3Params {
  method: "geetest";
  gt: string;
  challenge: string;
  pageurl: string;
  api_server?: string;
}

export interface GeeTestV4Params {
  method: "geetest_v4";
  captcha_id: string;
  pageurl: string;
}

export interface ImageCaptchaParams {
  method: "base64";
  body: string;
  numeric?: "0" | "1" | "2" | "3" | "4";
  min_len?: string;
  max_len?: string;
  language?: "0" | "1" | "2";
}

export interface BLSParams {
  method: "bls";
  sitekey: string;
  pageurl: string;
}

export type CaptchaParams =
  | RecaptchaV2Params
  | RecaptchaV3Params
  | TurnstileParams
  | GeeTestV3Params
  | GeeTestV4Params
  | ImageCaptchaParams
  | BLSParams;

export interface SubmitResponse {
  status: 0 | 1;
  request: string;
}

export interface PollResponse {
  status: 0 | 1;
  request: string;
}

export interface GeeTestResult {
  geetest_challenge: string;
  geetest_validate: string;
  geetest_seccode: string;
}

export type SolveResult<T extends CaptchaParams> = T extends GeeTestV3Params
  ? GeeTestResult
  : string;

export type CaptchaErrorCode =
  | "ERROR_WRONG_USER_KEY"
  | "ERROR_KEY_DOES_NOT_EXIST"
  | "ERROR_ZERO_BALANCE"
  | "ERROR_NO_SLOT_AVAILABLE"
  | "ERROR_CAPTCHA_UNSOLVABLE"
  | "ERROR_BAD_PARAMETERS"
  | "ERROR_WRONG_CAPTCHA_ID"
  | "CAPCHA_NOT_READY";

Typed error classes

// errors.ts

export class CaptchaError extends Error {
  constructor(
    public readonly code: string,
    message?: string
  ) {
    super(message || code);
    this.name = "CaptchaError";
  }
}

export class RetriableError extends CaptchaError {
  constructor(code: string) {
    super(code, `Retriable: ${code}`);
    this.name = "RetriableError";
  }
}

export class FatalError extends CaptchaError {
  constructor(code: string) {
    super(code, `Fatal: ${code}`);
    this.name = "FatalError";
  }
}

export class TimeoutError extends CaptchaError {
  constructor(public readonly elapsed: number) {
    super("TIMEOUT", `Solve timed out after ${elapsed}ms`);
    this.name = "TimeoutError";
  }
}

const FATAL_ERRORS = new Set([
  "ERROR_WRONG_USER_KEY",
  "ERROR_KEY_DOES_NOT_EXIST",
  "ERROR_ZERO_BALANCE",
  "ERROR_CAPTCHA_UNSOLVABLE",
  "ERROR_BAD_PARAMETERS",
]);

export function classifyError(code: string): never {
  if (FATAL_ERRORS.has(code)) {
    throw new FatalError(code);
  }
  throw new RetriableError(code);
}

Type-safe solver class

// solver.ts

import type {
  CaptchaParams,
  SubmitResponse,
  PollResponse,
  GeeTestV3Params,
  GeeTestResult,
  SolveResult,
} from "./captcha-types";
import { CaptchaError, FatalError, TimeoutError, classifyError } from "./errors";

interface SolverOptions {
  apiKey: string;
  pollInterval?: number;
  maxPollTime?: number;
  maxRetries?: number;
}

export class CaptchaAISolver {
  private readonly apiKey: string;
  private readonly pollInterval: number;
  private readonly maxPollTime: number;
  private readonly maxRetries: number;

  constructor(options: SolverOptions) {
    this.apiKey = options.apiKey;
    this.pollInterval = options.pollInterval ?? 5000;
    this.maxPollTime = options.maxPollTime ?? 150000;
    this.maxRetries = options.maxRetries ?? 3;
  }

  async solve<T extends CaptchaParams>(params: T): Promise<SolveResult<T>> {
    const taskId = await this.submit(params);
    return await this.poll<T>(taskId);
  }

  private async submit(params: CaptchaParams): Promise<string> {
    const { method, ...rest } = params;
    const body = new URLSearchParams({
      key: this.apiKey,
      method,
      json: "1",
      ...Object.fromEntries(
        Object.entries(rest).filter(([, v]) => v !== undefined)
      ),
    });

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      const resp = await fetch("https://ocr.captchaai.com/in.php", {
        method: "POST",
        body,
      });
      const data: SubmitResponse = await resp.json();

      if (data.status === 1) {
        return data.request;
      }

      if (data.request === "ERROR_NO_SLOT_AVAILABLE" && attempt < this.maxRetries) {
        await this.sleep(3000 * (attempt + 1));
        continue;
      }

      classifyError(data.request);
    }

    throw new CaptchaError("MAX_RETRIES", "Max submit retries exceeded");
  }

  private async poll<T extends CaptchaParams>(taskId: string): Promise<SolveResult<T>> {
    const start = Date.now();
    const params = new URLSearchParams({
      key: this.apiKey,
      action: "get",
      id: taskId,
      json: "1",
    });

    while (Date.now() - start < this.maxPollTime) {
      await this.sleep(this.pollInterval);

      const resp = await fetch(`https://ocr.captchaai.com/res.php?${params}`);
      const data: PollResponse = await resp.json();

      if (data.status === 1) {
        // GeeTest returns JSON, others return a string token
        try {
          const parsed = JSON.parse(data.request);
          if (parsed.geetest_challenge) {
            return parsed as SolveResult<T>;
          }
        } catch {}
        return data.request as SolveResult<T>;
      }

      if (data.request === "CAPCHA_NOT_READY") {
        continue;
      }

      classifyError(data.request);
    }

    throw new TimeoutError(Date.now() - start);
  }

  async getBalance(): Promise<number> {
    const resp = await fetch(
      `https://ocr.captchaai.com/res.php?${new URLSearchParams({
        key: this.apiKey,
        action: "getbalance",
        json: "1",
      })}`
    );
    const data = await resp.json();
    return parseFloat(data.request);
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

Usage examples

reCAPTCHA v2

const solver = new CaptchaAISolver({ apiKey: "YOUR_API_KEY" });

// TypeScript enforces correct parameters
const token = await solver.solve({
  method: "userrecaptcha",
  googlekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
  pageurl: "https://example.com/login",
});
// token is typed as string

console.log(`Token: ${token.substring(0, 50)}...`);

Cloudflare Turnstile

const token = await solver.solve({
  method: "turnstile",
  sitekey: "0x4AAAAAAABS7vwvV6VFfMcD",
  pageurl: "https://example.com/login",
  action: "login",
});

GeeTest v3

const result = await solver.solve({
  method: "geetest",
  gt: "81dc9bdb52d04dc20036dbd8313ed055",
  challenge: "d93591bdf7860e1e4ee2fca799911587",
  pageurl: "https://example.com",
});
// result is typed as GeeTestResult
console.log(result.geetest_validate);

Image CAPTCHA

const answer = await solver.solve({
  method: "base64",
  body: imageBase64String,
  numeric: "1", // Numbers only
  min_len: "4",
  max_len: "6",
});

Compile-time safety examples

// These will cause TypeScript errors:

// Missing required field
await solver.solve({
  method: "userrecaptcha",
  pageurl: "https://example.com",
  // Error: Property 'googlekey' is missing
});

// Wrong parameter for method
await solver.solve({
  method: "turnstile",
  googlekey: "key", // Error: 'googlekey' does not exist on TurnstileParams
  pageurl: "https://example.com",
});

// Invalid method name
await solver.solve({
  method: "invalid_method", // Error: not assignable to CaptchaMethod
  pageurl: "https://example.com",
});

Generic batch solver

interface BatchTask<T extends CaptchaParams> {
  id: string;
  params: T;
}

interface BatchResult<T extends CaptchaParams> {
  id: string;
  status: "solved" | "error";
  result?: SolveResult<T>;
  error?: string;
}

async function solveBatch<T extends CaptchaParams>(
  solver: CaptchaAISolver,
  tasks: BatchTask<T>[],
  maxConcurrent = 5
): Promise<BatchResult<T>[]> {
  const results: BatchResult<T>[] = [];
  const queue = [...tasks];

  const worker = async () => {
    while (queue.length > 0) {
      const task = queue.shift();
      if (!task) break;

      try {
        const result = await solver.solve(task.params);
        results.push({ id: task.id, status: "solved", result });
      } catch (error) {
        results.push({
          id: task.id,
          status: "error",
          error: error instanceof Error ? error.message : String(error),
        });
      }
    }
  };

  const workers = Array.from(
    { length: Math.min(maxConcurrent, tasks.length) },
    () => worker()
  );
  await Promise.all(workers);

  return results;
}

// Usage — fully typed
const results = await solveBatch(solver, [
  {
    id: "task_1",
    params: {
      method: "userrecaptcha",
      googlekey: "KEY_1",
      pageurl: "https://example.com/1",
    },
  },
  {
    id: "task_2",
    params: {
      method: "turnstile",
      sitekey: "0xKEY_2",
      pageurl: "https://example.com/2",
    },
  },
]);

Troubleshooting

Symptom Cause Fix
Type error on solve() Wrong params for method Match params to the method's interface
SolveResult is string | GeeTestResult Generic not narrowed Pass explicit type param or use discriminated union
fetch not found Missing types Add "lib": ["ES2022", "DOM"] to tsconfig.json
JSON parse error Response not JSON Check resp.ok before parsing
Runtime error despite types API behavior changed Types don't cover runtime — use try/catch

Frequently asked questions

Is there an official TypeScript SDK for CaptchaAI?

Not currently. The typed client in this guide provides full type safety using the REST API directly.

Can I use this with Deno or Bun?

Yes. The code uses standard fetch and TypeScript — it works in Deno, Bun, and Node 18+.

How do I handle union types in solve results?

Use the discriminated union pattern: check params.method to narrow the result type. For GeeTest, the result is GeeTestResult; for everything else, it's string.


Summary

TypeScript + CaptchaAI provides compile-time safety for CAPTCHA solving: the typed CaptchaAISolver class prevents wrong parameters, invalid methods, and missing fields. Use generics for batch processing and discriminated unions for type-safe results.

Discussions (0)

No comments yet.

Related Posts

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 reCAPTCHA v2 Cloudflare Turnstile
Feb 05, 2026
Reference CAPTCHA Token Injection Methods Reference
Complete reference for injecting solved CAPTCHA tokens into web pages.

Complete reference for injecting solved CAPTCHA tokens into web pages. Covers re CAPTCHA, Turnstile, and Cloud...

Automation Python reCAPTCHA v2
Apr 08, 2026
Tutorials Pytest Fixtures for CaptchaAI API Testing
Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI.

Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI. Covers mocking, live integra...

Automation Python reCAPTCHA v2
Apr 08, 2026
Reference Browser Session Persistence for CAPTCHA Workflows
Manage browser sessions, cookies, and storage across CAPTCHA-solving runs to reduce repeat challenges and maintain authenticated state.

Manage browser sessions, cookies, and storage across CAPTCHA-solving runs to reduce repeat challenges and main...

Automation Python reCAPTCHA v2
Feb 24, 2026
Integrations Browser Profile Isolation + CaptchaAI Integration
Browser profile isolation tools create distinct browser environments with unique fingerprints per session.

Browser profile isolation tools create distinct browser environments with unique fingerprints per session. Com...

Automation Python reCAPTCHA v2
Feb 21, 2026
Comparisons WebDriver vs Chrome DevTools Protocol for CAPTCHA Automation
Compare Web Driver and Chrome Dev Tools Protocol (CDP) for CAPTCHA automation — detection, performance, capabilities, and when to use each with Captcha AI.

Compare Web Driver and Chrome Dev Tools Protocol (CDP) for CAPTCHA automation — detection, performance, capabi...

Automation Python reCAPTCHA v2
Mar 27, 2026
Tutorials CAPTCHA Handling in Flask Applications with CaptchaAI
Integrate Captcha AI into Flask applications for automated CAPTCHA solving.

Integrate Captcha AI into Flask applications for automated CAPTCHA solving. Includes service class, API endpoi...

Automation Cloudflare Turnstile
Mar 17, 2026
Tutorials Caching CAPTCHA Tokens for Reuse
Cache and reuse CAPTCHA tokens with Captcha AI to reduce API calls and costs.

Cache and reuse CAPTCHA tokens with Captcha AI to reduce API calls and costs. Covers token lifetimes, cache st...

Automation Python reCAPTCHA v2
Feb 15, 2026
Use Cases Event Ticket Monitoring with CAPTCHA Handling
Build an event ticket availability monitor that handles CAPTCHAs using Captcha AI.

Build an event ticket availability monitor that handles CAPTCHAs using Captcha AI. Python workflow for checkin...

Automation Python reCAPTCHA v2
Jan 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 GeeTest Token Injection in Browser Automation Frameworks
how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the three-value response, callback triggering, and form submissi...

Learn how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the thre...

Automation Python Testing
Jan 18, 2026
Tutorials Securing CaptchaAI Credentials in Environment Variables
Store Captcha AI API keys securely using environment variables, .env files, Docker secrets, and cloud secret managers instead of hardcoding.

Store Captcha AI API keys securely using environment variables, .env files, Docker secrets, and cloud secret m...

Automation Python reCAPTCHA v2
Feb 12, 2026