Skip to content

CAPTCHA and AI functions#

Functions that solve captchas through third-party services and call AI models to answer questions, read images or generate images. You need your own account and API key with those services; 9Hits does not provide keys.

API keys in campaign macros

A 9Hits campaign macro runs on the computers of other people who run the Viewer, so any API key written in the macro is sent to those computers too. Use the functions that need an API key in 9Login scripts instead: a 9Login script runs on your own computer.

Solving captchas#

SolveRecaptcha and SolvehCaptcha read the captcha details from the page, send them to the service you choose, then fill the result into the page. The _2Captcha*, AC* and AZ* functions call one service directly (2Captcha, Anti-Captcha, Azcaptcha). The ReportBad/ReportIncorrect functions tell the service that a solve was wrong.

SolveRecaptcha#

Solves the reCAPTCHA on the open page: finds the site key, sends it to the solving service you choose, waits for the result, then fills the token into the page. After that, submit the form yourself with a click function; if the page has no submit button, call TryToCallRecaptchaCallBack.

Syntax
await SolveRecaptcha(service, apiKey, timeout=300, overrideApiServer=null);
Parameters
Name Description
service Solving service: "2captcha" or "anti-captcha". Any other value makes the function fail immediately.
apiKey Your API key for that service.
timeout Maximum time to wait for the answer, in seconds. Default 300.
overrideApiServer An alternative API address, for when you use another service whose API works like 2Captcha's or Anti-Captcha's. The default null uses the service's own address.
Returns

The service's result object when the captcha is solved, false on failure (the reason is written to the log). The token is in result.request for 2Captcha and in result.solution.gRecaptchaResponse for Anti-Captcha.

Example
1
2
3
4
5
6
7
8
9
// try it on https://www.google.com/recaptcha/api2/demo
await WaitForLoading();
const result = await SolveRecaptcha("2captcha", "YOUR_API_KEY");
// const result = await SolveRecaptcha("anti-captcha", "YOUR_API_KEY");
if (!result) { Log("Captcha solving failed"); return; }
await ClickBySelector("#recaptcha-demo-submit"); // click the submit button

// result with 2Captcha:     {status: 1, request: "TOKEN", captchaId: "71006265012"}
// result with Anti-Captcha: {errorId: 0, status: "ready", solution: {gRecaptchaResponse: "TOKEN", ...}, taskId: 685667638, ...}

SolvehCaptcha#

Same as SolveRecaptcha, but for hCaptcha: finds the site key, sends it to the solving service, waits for the result, then fills the token into the page. After that, submit the form yourself.

Syntax
await SolvehCaptcha(service, apiKey, timeout=300, overrideApiServer=null);
Parameters
Name Description
service Solving service: "2captcha" or "anti-captcha". Any other value makes the function fail immediately.
apiKey Your API key for that service.
timeout Maximum time to wait for the answer, in seconds. Default 300.
overrideApiServer An alternative API address, for when you use another service whose API works like 2Captcha's or Anti-Captcha's. The default null uses the service's own address.
Returns

Same as SolveRecaptcha: the service's result object when the captcha is solved, false on failure. The token is in result.request for 2Captcha and in result.solution.gRecaptchaResponse for Anti-Captcha.

Example
1
2
3
4
5
// try it on https://accounts.hcaptcha.com/demo
await WaitForLoading();
const result = await SolvehCaptcha("2captcha", "YOUR_API_KEY");
// const result = await SolvehCaptcha("anti-captcha", "YOUR_API_KEY");
if (result) await ClickById("hcaptcha-demo-submit"); // click the submit button

SolveImageCaptcha#

This function does not work yet: calling it solves nothing and returns nothing. To solve an image captcha, capture the image with CaptureElement and send it with _2CaptchaSolve or ACSolve, as in the example below.

Syntax
await SolveImageCaptcha(imgSelector, resultSelector, service, apiKey, timeout=300, overrideApiServer=null);
Parameters
Name Description
imgSelector CSS selector of the captcha image.
resultSelector CSS selector of the answer input.
service "2captcha" or "anti-captcha".
apiKey Your API key for that service.
timeout Maximum time to wait for the answer, in seconds. Default 300.
overrideApiServer An alternative API address for the service. Default null.
Example
1
2
3
4
// workaround: capture the captcha image and send it for solving yourself
const img = await CaptureElement("#captcha-image");
const r = await _2CaptchaSolve({key: "YOUR_API_KEY", method: "base64", json: 1, body: img}, 120);
if (r.status) await SetById("captcha-code", "value", r.request); // fill in the answer

TryToCallRecaptchaCallBack#

Calls the page's reCAPTCHA callback function with the solved token. Use it on pages that have no submit button, or that do not react when only the token is filled in. response is the token string, not the whole result object.

Syntax
await TryToCallRecaptchaCallBack(response, frameSearch="", frameSearchType="");
Parameters
Name Description
response The solved reCAPTCHA token: result.request from 2Captcha, or result.solution.gRecaptchaResponse from Anti-Captcha.
frameSearch Condition for finding the iframe that holds the reCAPTCHA, used together with frameSearchType; leave it empty for the main frame. See Iframes, selectors and waiting.
frameSearchType How frameSearch is matched: "src-starts", "src-ends", "src-equals", "src-contains", "src-regex" or "frame-path". You must set it when you use frameSearch: if it is empty, the function ignores frameSearch and works on the main frame.
Example
// solve with 2Captcha, then call the page's callback
const result = await SolveRecaptcha("2captcha", "YOUR_API_KEY");
if (result) await TryToCallRecaptchaCallBack(result.request);

// with Anti-Captcha, the token is in solution.gRecaptchaResponse
const r2 = await ACSolve({
    clientKey: "YOUR_API_KEY",
    task: {type: "NoCaptchaTaskProxyless", websiteURL: "https://example.com/login", websiteKey: "SITE_KEY"}
}, 600);
if (r2.errorId === 0) await TryToCallRecaptchaCallBack(r2.solution.gRecaptchaResponse);

_2CaptchaSolve#

Sends a solve request to 2Captcha and waits until there is an answer. It works for every captcha type that 2Captcha supports (image, reCAPTCHA, hCaptcha...); you only need to set params according to the 2Captcha API documentation.

Syntax
await _2CaptchaSolve(params, timeout);
Parameters
Name Description
params Parameter object sent to 2Captcha, following the 2Captcha API documentation. It must contain key (your API key), method (for example "base64" for images, "userrecaptcha" for reCAPTCHA) and json: 1, plus the captcha data that the method needs.
timeout Maximum time to wait for the answer, in seconds. Empty or 0 waits 150 seconds.
Returns

Object {status, request, captchaId}. When the captcha is solved, status is 1, request is the answer (or token) and captchaId is the ID of the solve, used by _2CaptchaReportBad. On failure, status is 0 and request is the reason: a 2Captcha error code, or "Timed out" when the time runs out.

Example
// capture the captcha image and send it to 2Captcha
const captBase64 = await CaptureElement("#captcha-image");
const result = await _2CaptchaSolve({
    key: "2CAPTCHA_API_KEY",
    method: "base64",
    json: 1,
    body: captBase64
}, 120);
if (result.status) await SetById("captcha-code", "value", result.request);
else Log("Error:", result.request);
// result = {status: 1, request: "VMXKDG", captchaId: "1241352612"}

_2CaptchaReportBad#

Tells 2Captcha that an answer was wrong, so you are not charged for that solve. Report only when you are sure the answer is wrong; do not abuse it.

Syntax
await _2CaptchaReportBad(key, captchaId);
Parameters
Name Description
key Your 2Captcha API key.
captchaId The ID of the solve: captchaId in the result of _2CaptchaSolve, or of SolveRecaptcha when it uses 2Captcha.
Returns

The 2Captcha HTTP response as {status, headers, body}, like HttpRequest; you usually do not need it.

Example
1
2
3
4
5
6
7
8
const captBase64 = await CaptureElement("#captcha-image");
const result = await _2CaptchaSolve({key: "2CAPTCHA_API_KEY", method: "base64", json: 1, body: captBase64}, 120);
await SetById("captcha-code", "value", result.request);
await ClickBySelector("#submit");

// the page says the code is wrong: report it to 2Captcha
const wrong = await WaitForElmtBySelector(".captcha-error", {exists: true}, 5);
if (wrong) await _2CaptchaReportBad("2CAPTCHA_API_KEY", result.captchaId);

ACSolve#

Sends a task to Anti-Captcha and waits until there is an answer. task.type decides the captcha type, following the Anti-Captcha API documentation.

Syntax
await ACSolve(params, timeout);
Parameters
Name Description
params Object sent to Anti-Captcha, following the Anti-Captcha API documentation: clientKey is your API key, task describes the captcha to solve (for example {type: "ImageToTextTask", body: base64 image}). If it is not an object, the function returns an error immediately.
timeout Maximum time to wait for the answer, in seconds. Empty or 0 waits 150 seconds.
Returns

The Anti-Captcha result object. When the captcha is solved, errorId is 0, status is "ready", the answer is in solution (solution.text for image captchas, solution.gRecaptchaResponse for reCAPTCHA) and taskId is used to report a wrong solve. On an error or timeout, errorId is not 0 and errorDescription gives the reason.

Example
// capture the captcha image and send it to Anti-Captcha
const image = await CaptureElement("#captcha-image");
const result = await ACSolve({
    clientKey: "YOUR_API_KEY",
    task: {type: "ImageToTextTask", body: image}
}, 150);
if (result.errorId === 0) await SetById("captcha-code", "value", result.solution.text);
else Log("Error:", result.errorDescription);

/* result:
{
    errorId: 0,
    status: "ready",
    solution: {text: "deditur", url: "http://61.39.233.233/1/147220556452507.jpg"},
    cost: "0.000700",
    createTime: 1472205564,
    endTime: 1472205570,
    taskId: 1234567
}
*/

ACReportIncorrectImage#

Tells Anti-Captcha that an image captcha was solved wrong. Report only when you are sure the answer is wrong; do not abuse it.

Syntax
await ACReportIncorrectImage(clientKey, taskId);
Parameters
Name Description
clientKey Your Anti-Captcha API key.
taskId The task ID: taskId in the result of ACSolve.
Returns

The Anti-Captcha HTTP response as {status, headers, body}; you usually do not need it.

Example
1
2
3
4
5
6
7
8
const image = await CaptureElement("#captcha-image");
const result = await ACSolve({clientKey: "YOUR_API_KEY", task: {type: "ImageToTextTask", body: image}}, 150);
await SetById("captcha-code", "value", result.solution.text);
await ClickBySelector("#submit");

// the page says the code is wrong: report it to Anti-Captcha
const wrong = await WaitForElmtBySelector(".captcha-error", {exists: true}, 5);
if (wrong) await ACReportIncorrectImage("YOUR_API_KEY", result.taskId);

ACReportIncorrectRecaptcha#

Tells Anti-Captcha that a reCAPTCHA was solved wrong. The function sends the report and moves on right away, without returning a result. Report only when you are sure; do not abuse it.

Syntax
ACReportIncorrectRecaptcha(clientKey, taskId);
Parameters
Name Description
clientKey Your Anti-Captcha API key.
taskId The task ID: taskId in the result of ACSolve, or of SolveRecaptcha when it uses Anti-Captcha.
Example
1
2
3
4
5
6
const result = await SolveRecaptcha("anti-captcha", "YOUR_API_KEY");
await ClickBySelector("#submit");

// the page says the captcha is not correct: report it to Anti-Captcha
const wrong = await WaitForElmtBySelector(".captcha-error", {exists: true}, 5);
if (result && wrong) ACReportIncorrectRecaptcha("YOUR_API_KEY", result.taskId);

AZSolve#

Sends a solve request to Azcaptcha and waits until there is an answer. It works like _2CaptchaSolve: params follows the Azcaptcha API documentation, which uses the same style as 2Captcha's.

Syntax
await AZSolve(params, timeout);
Parameters
Name Description
params Parameter object sent to Azcaptcha: key is your API key, plus method (for example "base64") and the captcha data (for example body). If you do not set json, the function adds json: 1 itself.
timeout Maximum time to wait for the answer, in seconds. Empty or 0 waits 300 seconds.
Returns

Object {status, request, captchaId}, the same shape as _2CaptchaSolve: status is 1 when the captcha is solved and request is the answer; on failure, status is 0 and request is the reason. captchaId is used by AZReportBad.

Example
1
2
3
4
5
const captBase64 = await CaptureElement("#captcha-image");
const result = await AZSolve({key: "AZCAPTCHA_API_KEY", method: "base64", body: captBase64}, 120);
if (result.status) await SetById("captcha-code", "value", result.request);
else Log("Error:", result.request);
// result = {status: 1, request: "VMXKDG", captchaId: "1241352612"}

AZReportBad#

Tells Azcaptcha that a solve was wrong. Report only when you are sure the answer is wrong; do not abuse it.

Syntax
await AZReportBad(key, captchaId);
Parameters
Name Description
key Your Azcaptcha API key.
captchaId The ID of the solve: captchaId in the result of AZSolve.
Returns

The Azcaptcha HTTP response as {status, headers, body}; you usually do not need it.

Example
1
2
3
4
5
6
7
8
const captBase64 = await CaptureElement("#captcha-image");
const result = await AZSolve({key: "AZCAPTCHA_API_KEY", method: "base64", body: captBase64}, 120);
await SetById("captcha-code", "value", result.request);
await ClickBySelector("#submit");

// the page says the code is wrong: report it to Azcaptcha
const wrong = await WaitForElmtBySelector(".captcha-error", {exists: true}, 5);
if (wrong) await AZReportBad("AZCAPTCHA_API_KEY", result.captchaId);

Calling AI#

PromptAI, GenImageAI and ListAIModels can call many AI providers with the same kind of parameters, and they always return results in the same shape. These functions do not throw: on an error, ok is false and the reason is in error.

PromptAI#

Sends a question to an AI model and gets the answer, optionally with images (for example a screenshot of the open page). All options go in one options object; always check ok before you read text.

Syntax
await PromptAI(options);
Parameters
Name Description
options.provider Provider: "openai", "anthropic", "gemini", "openrouter", "deepseek", "groq", "xai", "mistral", "together", "perplexity", "ollama" or "lmstudio". The names "claude", "google", "grok", "local" (meaning ollama) and "lm-studio" also work. If both provider and endpoint are empty, openai is used.
options.apiKey Your API key with that provider.
options.model Model name. If empty, the default is used: gpt-4o-mini (openai), deepseek-chat (deepseek), grok-4 (xai), claude-sonnet-5 (anthropic), gemini-2.5-flash (gemini). For the other providers, and when you use endpoint, it is required. Get the names with ListAIModels.
options.prompt The question, sent as a user message. You need prompt or messages.
options.system System instructions (system prompt), for example a role or how to answer.
options.messages A whole conversation: an array of {role, content, images} where role is "user", "assistant" or "system"; a string element counts as a user message. If prompt is also set, prompt is added at the end. "system" messages are merged into system.
options.images Images to send along, one image or an array: a data URL, a base64 string (like the result of CapturePage), an http(s) URL or an ArrayBuffer. The images are attached to the last user message. image also works.
options.json true to ask for a JSON answer, or a JSON Schema to enforce the exact structure. With anthropic this is only an instruction added to the system prompt, so you still have to check the returned json yourself.
options.temperature How random the answer is; smaller numbers give more consistent answers. Empty uses the provider's default.
options.topP Top-p sampling parameter. Empty uses the provider's default.
options.maxTokens Maximum number of tokens in the answer. Empty lets the provider decide; anthropic requires a limit, so its default is 4096.
options.stop A string or an array of strings; the model stops when it is about to write that string.
options.timeout Timeout for each call, in milliseconds. Default 60000. Each retry gets its own timeout.
options.retries How many times to retry automatically on a network error, rate limiting (HTTP 429) or a temporary server error. Default 2.
options.useProxy true to call through the session's proxy. Default false: call directly from the computer.
options.proxy A proxy for this call only: {type, host, port, username, password}, where type is "http" or "socks5". When proxy is set, useProxy is not used.
options.endpoint Base API address for a service that is not in the provider list, for example "https://my-llm.example.com/v1". When you use it, you must set model. baseUrl also works.
options.kind API style of endpoint: "openai", "anthropic" or "gemini". If empty, it is guessed from the address; if it cannot be guessed, the openai style is assumed.
options.headers Extra HTTP headers for the request, as {name: value}.
options.body Object merged into the request body last, overriding fields with the same name; use it for provider-specific parameters.
options.path Request path to use instead of the default, appended to the base address, for example "/chat/completions".
Returns

Object {ok, text, json, usage, provider, model, finishReason, status, error, raw}:

  • ok: true when the call succeeds.
  • text: the answer; an empty string on error, so always check ok first.
  • json: the parsed object when you turn on json or when the answer looks like JSON; null when there is none or it cannot be parsed. Having json does not mean the model followed the schema.
  • usage: token counts {input, output, total}; 0 when the provider does not report them.
  • provider, model: the provider and model that answered.
  • finishReason: why the model stopped, in the provider's own terms.
  • status: the HTTP code of the last call, 0 when it could not connect.
  • error: the error reason; an empty string on success.
  • raw: the provider's original response.
Examples
// a simple question
const r = await PromptAI({
    provider: "openai",
    apiKey: "YOUR_API_KEY",
    model: "gpt-4o-mini",
    prompt: "Summarize this page in one sentence"
});
if (!r.ok) { Log("AI error:", r.error); return; }
Log(r.text, "- tokens:", r.usage.total);

// ask with a screenshot of the open page
const shot = await CapturePage(70);
const v = await PromptAI({provider: "gemini", apiKey: "YOUR_API_KEY", prompt: "What is on the screen?", images: shot});

// ask for JSON with an exact structure
const j = await PromptAI({
    provider: "openai",
    apiKey: "YOUR_API_KEY",
    prompt: "Get the product name and price: 9Hits T-shirt, 150000 VND",
    json: {
        type: "object",
        properties: {name: {type: "string"}, price: {type: "number"}},
        required: ["name", "price"],
        additionalProperties: false
    }
});
if (j.ok && j.json) Log(j.json.name, j.json.price);
// j.json = {name: "9Hits T-shirt", price: 150000}

// multi-turn conversation
const c = await PromptAI({
    provider: "openrouter",
    apiKey: "YOUR_API_KEY",
    model: "openai/gpt-4o-mini",
    system: "Answer briefly in English.",
    messages: [
        {role: "user", content: "Suggest a title for an article about Da Lat"},
        {role: "assistant", content: "Da Lat in the mist"},
        {role: "user", content: "Even shorter"}
    ]
});

GenImageAI#

Creates images from a text description. It works with gemini and OpenAI-compatible providers; anthropic cannot create images and returns an error immediately. With gemini only prompt is sent, and the model decides how many images come back.

Syntax
await GenImageAI(options);
Parameters
Name Description
options.provider Provider, as in PromptAI.
options.apiKey Your API key with that provider.
options.model Image model. If empty, gpt-image-1 is used with OpenAI-compatible providers and gemini-2.5-flash-image with gemini; these differ from the chat models of PromptAI.
options.prompt Description of the image to create. Required.
options.n Number of images to create. Default 1. Only applies to OpenAI-compatible providers.
options.size Image size. Default "1024x1024". Only applies to OpenAI-compatible providers.
options.quality Image quality; the provider defines the values. Only applies to OpenAI-compatible providers.
options.style Image style; the provider defines the values. Only applies to OpenAI-compatible providers.
options.background Image background type; the provider defines the values. Only applies to OpenAI-compatible providers.
options.format "url" (get an image link) or "b64_json" (get base64 data). Set it only when you need it, because some models return an error when they receive this field (such as gpt-image-1, which always returns base64). responseFormat also works.
options.timeout Timeout for each call, in milliseconds. Default 120000, because image generation is slow; counting the retries, a failing call can take several minutes.
options.endpoint, options.kind, options.retries, options.useProxy, options.proxy, options.headers, options.body, options.path Same as in PromptAI.
Returns

Object {ok, images, provider, model, status, error, raw}:

  • ok: true when there is at least one image. A successful call with no images still returns false.
  • images: an array of {url, b64, mime, revisedPrompt}. Depending on the provider, each image has url or b64, so read both. mime is the image type, for example "image/png"; revisedPrompt is the description as rewritten by the provider, if any.
  • provider, model, status, error, raw: same as in PromptAI.
Examples
// create an image and save it to a file (writing files only works in 9Login scripts or when test-running in Studio)
const r = await GenImageAI({provider: "openai", apiKey: "YOUR_API_KEY", prompt: "a red bicycle, studio lighting"});
if (!r.ok) { Log("Image generation error:", r.error); return; }
const img = r.images[0];
if (img.b64) WriteFile("D:/out.png", Base64Decode(img.b64, false));
else Log("Image URL:", img.url);

// two portrait images, high quality
const r2 = await GenImageAI({
    provider: "openai",
    apiKey: "YOUR_API_KEY",
    prompt: "a cat logo, minimalist style",
    n: 2,
    size: "1024x1536",
    quality: "high"
});

ListAIModels#

Lists the models that your API key can use with a provider. Call it before you hard-code a model name into a script, because each account and each key may have access to different models.

Syntax
await ListAIModels(options);
Parameters
Name Description
options.provider Provider, as in PromptAI. model is not needed.
options.apiKey Your API key with that provider.
options.filter Keep only the models whose name contains this string, case-insensitive.
options.timeout Timeout for each call, in milliseconds. Default 30000.
options.endpoint, options.kind, options.retries, options.useProxy, options.proxy, options.headers Same as in PromptAI.
Returns

Object {ok, ids, models, provider, status, error, raw}:

  • ids: an array of model names in alphabetical order, ready to use as model for PromptAI.
  • models: an array of {id, name, created}, where name is the display name and created is the creation time reported by the provider (0 if there is none).
  • ok, provider, status, error, raw: same as in PromptAI.

An endpoint that does not support listing models returns ok: false, not an empty list.

Examples
1
2
3
4
5
6
7
8
// the Claude models available through OpenRouter
const r = await ListAIModels({provider: "openrouter", apiKey: "YOUR_API_KEY", filter: "claude"});
Log(r.ok ? r.ids.join(", ") : r.error);
// r.ids = ["anthropic/claude-3.5-haiku", "anthropic/claude-sonnet-4", ...]

// check that a model is available before you call it
const m = await ListAIModels({provider: "openai", apiKey: "YOUR_API_KEY"});
const model = (m.ok && m.ids.includes("gpt-4o")) ? "gpt-4o" : "gpt-4o-mini";