TypeScript generics let you build a CAPTCHA client where the compiler enforces correct parameter types for each CAPTCHA method. Pass wrong parameters for a Turnstile solve? The compiler catches it before runtime.
Type definitions
// types.ts
export interface RecaptchaV2Params {
method: "userrecaptcha";
googlekey: string;
pageurl: string;
invisible?: boolean;
"data-s"?: string;
proxy?: string;
proxytype?: "HTTP" | "HTTPS" | "SOCKS4" | "SOCKS5";
}
export interface RecaptchaV3Params {
method: "userrecaptcha";
version: "v3";
googlekey: string;
pageurl: string;
action?: string;
min_score?: number;
}
export interface TurnstileParams {
method: "turnstile";
sitekey: string;
pageurl: string;
action?: string;
data?: string;
}
export interface ImageParams {
method: "base64";
body: string;
phrase?: boolean;
regsense?: boolean;
numeric?: 0 | 1 | 2;
min_len?: number;
max_len?: number;
}
export type CaptchaParams =
| RecaptchaV2Params
| RecaptchaV3Params
| TurnstileParams
| ImageParams;
export interface SolveResult {
taskId: string;
token: string;
solveTime: number;
}
export interface APIResponse {
status: number;
request: string;
}
export class CaptchaAIError extends Error {
constructor(public code: string) {
super(`CaptchaAI error: ${code}`);
this.name = "CaptchaAIError";
}
}
export class TimeoutError extends CaptchaAIError {
constructor(taskId: string) {
super(`TIMEOUT: task ${taskId}`);
this.name = "TimeoutError";
}
}
Generic client
// client.ts
import {
CaptchaParams,
SolveResult,
APIResponse,
CaptchaAIError,
TimeoutError,
} from "./types";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
interface ClientOptions {
apiKey: string;
timeout?: number;
pollInterval?: number;
}
export class CaptchaAI {
private apiKey: string;
private timeout: number;
private pollInterval: number;
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.timeout = options.timeout ?? 120_000;
this.pollInterval = options.pollInterval ?? 5_000;
}
async solve<T extends CaptchaParams>(params: T): Promise<SolveResult> {
const start = Date.now();
// Build form body
const body = new URLSearchParams();
body.set("key", this.apiKey);
body.set("json", "1");
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
body.set(key, String(value));
}
}
// Submit
const submitResp = await fetch(SUBMIT_URL, {
method: "POST",
body,
});
const submitData: APIResponse = await submitResp.json();
if (submitData.status !== 1) {
throw new CaptchaAIError(submitData.request);
}
const taskId = submitData.request;
// Poll
const deadline = start + this.timeout;
while (Date.now() < deadline) {
await this.delay(this.pollInterval);
const url = new URL(RESULT_URL);
url.searchParams.set("key", this.apiKey);
url.searchParams.set("action", "get");
url.searchParams.set("id", taskId);
url.searchParams.set("json", "1");
const pollResp = await fetch(url.toString());
const pollData: APIResponse = await pollResp.json();
if (pollData.status === 1) {
return {
taskId,
token: pollData.request,
solveTime: (Date.now() - start) / 1000,
};
}
if (pollData.request !== "CAPCHA_NOT_READY") {
throw new CaptchaAIError(pollData.request);
}
}
throw new TimeoutError(taskId);
}
async getBalance(): Promise<number> {
const url = new URL(RESULT_URL);
url.searchParams.set("key", this.apiKey);
url.searchParams.set("action", "getbalance");
url.searchParams.set("json", "1");
const resp = await fetch(url.toString());
const data: APIResponse = await resp.json();
if (data.status !== 1) {
throw new CaptchaAIError(data.request);
}
return parseFloat(data.request);
}
async reportBad(taskId: string): Promise<void> {
const url = new URL(RESULT_URL);
url.searchParams.set("key", this.apiKey);
url.searchParams.set("action", "reportbad");
url.searchParams.set("id", taskId);
url.searchParams.set("json", "1");
await fetch(url.toString());
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
Type-safe usage
The generic solve<T> method enforces parameter shapes at compile time:
import { CaptchaAI } from "./client";
import type {
RecaptchaV2Params,
RecaptchaV3Params,
TurnstileParams,
ImageParams,
} from "./types";
const solver = new CaptchaAI({ apiKey: "YOUR_API_KEY" });
// reCAPTCHA v2 — compiler requires googlekey and pageurl
const v2Result = await solver.solve<RecaptchaV2Params>({
method: "userrecaptcha",
googlekey: "6Le-SITEKEY",
pageurl: "https://example.com",
});
console.log(`v2 token: ${v2Result.token.substring(0, 40)}...`);
// reCAPTCHA v3 — compiler requires version, action fields
const v3Result = await solver.solve<RecaptchaV3Params>({
method: "userrecaptcha",
version: "v3",
googlekey: "6Le-V3KEY",
pageurl: "https://example.com",
action: "login",
min_score: 0.7,
});
console.log(`v3 score token: ${v3Result.token.substring(0, 40)}...`);
// Turnstile — compiler requires sitekey (not googlekey)
const turnstileResult = await solver.solve<TurnstileParams>({
method: "turnstile",
sitekey: "0x4AAAAAAAB...",
pageurl: "https://example.com",
});
console.log(`Turnstile token: ${turnstileResult.token.substring(0, 40)}...`);
// Image — compiler requires body (base64)
const imageResult = await solver.solve<ImageParams>({
method: "base64",
body: "iVBORw0KGgoAAAANSUhEUg...",
});
console.log(`Image text: ${imageResult.token}`);
Compile-time error examples
// ERROR: Property 'sitekey' does not exist on type 'RecaptchaV2Params'
await solver.solve<RecaptchaV2Params>({
method: "userrecaptcha",
sitekey: "wrong-field", // should be 'googlekey'
pageurl: "https://example.com",
});
// ERROR: Property 'version' is missing in type
await solver.solve<RecaptchaV3Params>({
method: "userrecaptcha",
googlekey: "6Le-KEY",
pageurl: "https://example.com",
// missing: version: "v3"
});
Error handling with type narrowing
import { CaptchaAIError, TimeoutError } from "./types";
try {
const result = await solver.solve<RecaptchaV2Params>({
method: "userrecaptcha",
googlekey: "6Le-SITEKEY",
pageurl: "https://example.com",
});
console.log(`Solved: ${result.token.substring(0, 50)}...`);
} catch (error) {
if (error instanceof TimeoutError) {
console.error("Solve timed out — increase timeout or check parameters");
} else if (error instanceof CaptchaAIError) {
switch (error.code) {
case "ERROR_ZERO_BALANCE":
console.error("Add funds to your account");
break;
case "ERROR_WRONG_USER_KEY":
console.error("Check your API key");
break;
default:
console.error(`API error: ${error.code}`);
}
}
}
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
Type errors on solve() call |
Wrong parameter interface | Match the generic type to the method field |
fetch is not defined |
Node.js < 18 | Use Node.js 18+ or install node-fetch |
CaptchaAIError: ERROR_WRONG_USER_KEY |
Invalid API key | Verify key in CaptchaAI dashboard |
FAQ
Why use generics instead of separate methods?
Generics give you one solve() method that handles all types while the compiler enforces correct parameters. You get the simplicity of a single method with the safety of per-type validation.
Can I extend this with new CAPTCHA types?
Yes. Define a new params interface, add it to the CaptchaParams union, and the solve() method works with it automatically.
Build type-safe CAPTCHA solving into your TypeScript projects
Get your API key at captchaai.com.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.