Raw fetch calls work, but repeating the same submit-poll-parse pattern across every project wastes time. An SDK wraps the CaptchaAI API into clean, typed methods — client.solveRecaptchaV2() instead of manually constructing URL parameters and polling loops every time.
SDK Architecture
captchaai-sdk/
├── src/
│ ├── client.js # Main client class
│ ├── errors.js # Custom error classes
│ └── constants.js # API URLs and defaults
├── package.json
└── README.md
Error Classes
// src/errors.js
class CaptchaAIError extends Error {
constructor(message, code = null) {
super(message);
this.name = "CaptchaAIError";
this.code = code;
}
}
class SubmitError extends CaptchaAIError {
constructor(code) {
super(`Task submission failed: ${code}`, code);
this.name = "SubmitError";
}
}
class SolveError extends CaptchaAIError {
constructor(code) {
super(`Task solving failed: ${code}`, code);
this.name = "SolveError";
}
}
class TimeoutError extends CaptchaAIError {
constructor(taskId, timeoutMs) {
super(`Task ${taskId} timed out after ${timeoutMs}ms`);
this.name = "TimeoutError";
this.taskId = taskId;
}
}
class BalanceError extends CaptchaAIError {
constructor(balance) {
super(`Insufficient balance: $${balance}`);
this.name = "BalanceError";
this.balance = balance;
}
}
module.exports = { CaptchaAIError, SubmitError, SolveError, TimeoutError, BalanceError };
Constants
// src/constants.js
module.exports = {
SUBMIT_URL: "https://ocr.captchaai.com/in.php",
RESULT_URL: "https://ocr.captchaai.com/res.php",
DEFAULT_POLL_INTERVAL: 5000,
DEFAULT_TIMEOUT: 180000,
RETRYABLE_ERRORS: new Set([
"ERROR_NO_SLOT_AVAILABLE",
"ERROR_TOO_MUCH_REQUESTS",
]),
FATAL_ERRORS: new Set([
"ERROR_WRONG_USER_KEY",
"ERROR_KEY_DOES_NOT_EXIST",
"ERROR_ZERO_BALANCE",
"ERROR_IP_NOT_ALLOWED",
]),
};
Main Client
// src/client.js
const { SUBMIT_URL, RESULT_URL, DEFAULT_POLL_INTERVAL, DEFAULT_TIMEOUT, FATAL_ERRORS } = require("./constants");
const { SubmitError, SolveError, TimeoutError, BalanceError } = require("./errors");
class CaptchaAI {
/**
* @param {string} apiKey - Your CaptchaAI API key
* @param {object} [options]
* @param {number} [options.pollInterval=5000] - Milliseconds between poll requests
* @param {number} [options.timeout=180000] - Max wait time for a solution
*/
constructor(apiKey, options = {}) {
if (!apiKey) throw new Error("API key is required");
this.apiKey = apiKey;
this.pollInterval = options.pollInterval || DEFAULT_POLL_INTERVAL;
this.timeout = options.timeout || DEFAULT_TIMEOUT;
}
// --- Core methods ---
async _submit(params) {
const body = new URLSearchParams({
key: this.apiKey,
json: "1",
...params,
});
const response = await fetch(SUBMIT_URL, { method: "POST", body });
const result = await response.json();
if (result.status !== 1) {
throw new SubmitError(result.request);
}
return result.request; // task ID
}
async _poll(taskId) {
const start = Date.now();
while (Date.now() - start < this.timeout) {
await this._sleep(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 response = await fetch(url);
const result = await response.json();
if (result.request === "CAPCHA_NOT_READY") {
continue;
}
if (result.status === 1) {
return result.request; // solution token
}
throw new SolveError(result.request);
}
throw new TimeoutError(taskId, this.timeout);
}
async _solve(params) {
const taskId = await this._submit(params);
return this._poll(taskId);
}
_sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// --- Solver methods ---
/**
* Solve reCAPTCHA v2
* @param {string} sitekey - The site's reCAPTCHA sitekey
* @param {string} pageurl - The URL where the CAPTCHA appears
* @param {object} [options]
* @param {string} [options.cookies] - Cookies in "key=value; key2=value2" format
* @param {string} [options.userAgent] - Custom user agent
* @param {boolean} [options.invisible] - Set true for invisible reCAPTCHA
* @returns {Promise<string>} The solved token
*/
async solveRecaptchaV2(sitekey, pageurl, options = {}) {
const params = {
method: "userrecaptcha",
googlekey: sitekey,
pageurl,
};
if (options.cookies) params.cookies = options.cookies;
if (options.userAgent) params.userAgent = options.userAgent;
if (options.invisible) params.invisible = "1";
return this._solve(params);
}
/**
* Solve reCAPTCHA v3
* @param {string} sitekey
* @param {string} pageurl
* @param {object} [options]
* @param {string} [options.action] - The reCAPTCHA action value
* @param {number} [options.minScore] - Minimum acceptable score (0.1–0.9)
* @returns {Promise<string>}
*/
async solveRecaptchaV3(sitekey, pageurl, options = {}) {
const params = {
method: "userrecaptcha",
version: "v3",
googlekey: sitekey,
pageurl,
};
if (options.action) params.action = options.action;
if (options.minScore) params.min_score = String(options.minScore);
return this._solve(params);
}
/**
* Solve Cloudflare Turnstile
* @param {string} sitekey
* @param {string} pageurl
* @param {object} [options]
* @param {string} [options.action] - Turnstile action parameter
* @param {string} [options.cdata] - Turnstile cdata parameter
* @returns {Promise<string>}
*/
async solveTurnstile(sitekey, pageurl, options = {}) {
const params = {
method: "turnstile",
sitekey,
pageurl,
};
if (options.action) params.action = options.action;
if (options.cdata) params.data = options.cdata;
return this._solve(params);
}
/**
* Solve hCaptcha
* @param {string} sitekey
* @param {string} pageurl
* @returns {Promise<string>}
*/
async solveHCaptcha(sitekey, pageurl) {
return this._solve({
method: "hcaptcha",
sitekey,
pageurl,
});
}
/**
* Solve image/text CAPTCHA from base64
* @param {string} base64Image - Base64-encoded image (no data: prefix)
* @param {object} [options]
* @param {boolean} [options.caseSensitive] - Case-sensitive matching
* @param {number} [options.minLength] - Minimum answer length
* @param {number} [options.maxLength] - Maximum answer length
* @returns {Promise<string>}
*/
async solveImage(base64Image, options = {}) {
const params = {
method: "base64",
body: base64Image,
};
if (options.caseSensitive) params.regsense = "1";
if (options.minLength) params.min_len = String(options.minLength);
if (options.maxLength) params.max_len = String(options.maxLength);
return this._solve(params);
}
/**
* Solve GeeTest v3
* @param {string} gt - gt parameter
* @param {string} challenge - challenge parameter
* @param {string} pageurl
* @returns {Promise<string>}
*/
async solveGeeTestV3(gt, challenge, pageurl) {
return this._solve({
method: "geetest",
gt,
challenge,
pageurl,
});
}
// --- Utility methods ---
/**
* Get current account balance
* @returns {Promise<number>}
*/
async getBalance() {
const url = new URL(RESULT_URL);
url.searchParams.set("key", this.apiKey);
url.searchParams.set("action", "getbalance");
url.searchParams.set("json", "1");
const response = await fetch(url);
const result = await response.json();
if (result.status === 0 && FATAL_ERRORS.has(result.request)) {
throw new SubmitError(result.request);
}
return parseFloat(result.request);
}
/**
* Report a bad solution
* @param {string} taskId - The task ID to report
* @returns {Promise<boolean>}
*/
async reportBad(taskId) {
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");
const response = await fetch(url);
const result = await response.json();
return result.status === 1;
}
}
module.exports = { CaptchaAI };
Package Configuration
{
"name": "captchaai-sdk",
"version": "1.0.0",
"description": "Node.js SDK for CaptchaAI API",
"main": "src/client.js",
"exports": {
".": "./src/client.js",
"./errors": "./src/errors.js"
},
"engines": {
"node": ">=18.0.0"
},
"keywords": ["captcha", "captchaai", "recaptcha", "turnstile", "hcaptcha"],
"license": "MIT"
}
Usage Examples
const { CaptchaAI } = require("captchaai-sdk");
const { TimeoutError, SubmitError } = require("captchaai-sdk/errors");
const client = new CaptchaAI("YOUR_API_KEY", {
pollInterval: 5000,
timeout: 120000,
});
// Check balance
const balance = await client.getBalance();
console.log(`Balance: $${balance.toFixed(2)}`);
// Solve reCAPTCHA v2
try {
const token = await client.solveRecaptchaV2(
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"https://example.com/login",
{ cookies: "session=abc123" }
);
console.log(`Token: ${token.substring(0, 40)}...`);
} catch (err) {
if (err instanceof TimeoutError) {
console.error("Solve timed out");
} else if (err instanceof SubmitError) {
console.error(`API error: ${err.code}`);
}
}
// Solve Turnstile
const turnstileToken = await client.solveTurnstile(
"0x4AAAAAAADnPIDROrmt1Wwj",
"https://example.com/checkout"
);
// Solve image CAPTCHA
const fs = require("fs");
const imageBase64 = fs.readFileSync("captcha.png", "base64");
const text = await client.solveImage(imageBase64, { caseSensitive: true });
console.log(`Text: ${text}`);
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
SubmitError: ERROR_WRONG_USER_KEY |
Invalid API key | Verify key from CaptchaAI dashboard |
TimeoutError on every solve |
Timeout too short; network issues | Increase timeout option; check connectivity |
TypeError: fetch is not a function |
Node.js < 18 | Upgrade Node.js to 18+ or install node-fetch |
| Wrong token format returned | Used wrong method for CAPTCHA type | Match method to provider: userrecaptcha for reCAPTCHA, turnstile for Cloudflare |
SubmitError: ERROR_ZERO_BALANCE |
No funds | Top up balance at CaptchaAI dashboard |
FAQ
Should I publish this as an npm package?
For internal use, keep it as a local dependency. For distribution, add JSDoc types, tests, a README with examples, and publish to npm with npm publish. Consider TypeScript for the published version.
How do I add retry logic to the SDK?
Wrap _submit with a retry loop for ERROR_NO_SLOT_AVAILABLE. The SDK already handles polling retries; submission retries need explicit backoff (e.g., 5s, 10s, 15s delays).
Can I use this with TypeScript?
This JavaScript SDK works with TypeScript via JSDoc annotations. For a fully typed experience, see Type-Safe CaptchaAI Client with TypeScript Generics.
Related Articles
- Captchaai Ip Whitelisting Api Key Security
- Captchaai Api Key Rotation
- Captchaai Api Endpoint Mapping Competitors
Next Steps
Build your own CaptchaAI Node.js SDK — get your API key and start with the client class above.
Related guides:
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.