For AI agents: a documentation index is available at /llms.txt
Skip to main content

Solving CAPTCHAs with BAP

BAP solves CAPTCHAs inside the running session, so the page that was blocked is the page that continues once the challenge clears. This guide covers solve() for interactive challenges like Cloudflare and reCAPTCHA, and solveImageCaptcha() for image-plus-input forms.

Prerequisites
  • A Browserless API token from your account dashboard
  • The BAP SDK installed and connecting, from the BAP Quickstart
  • A plan with CAPTCHA solving, since solves consume units

Solve an interactive challenge

solve() waits for a CAPTCHA to appear, solves it, and resolves with what happened. With no type it auto-detects the challenge, which is the right default because protected sites rotate providers. Pass a type when you know the site and want to skip detection.

import Browserless from "@browserless.io/bap-ts";

const TOKEN = "YOUR_API_TOKEN_HERE";

const browser = Browserless.connect({
browserWSEndpoint: "wss://production-sfo.browserless.io/stealth/bql",
token: TOKEN,
});

try {
const page = await browser.newPage();
await page.goto("https://example.com/protected");

// Auto-detects the challenge type. timeout bounds the wait for the
// CAPTCHA to appear, not the solve itself.
const result = await page.solve({ timeout: 30_000 });

if (result.solved) {
// The session continues where the challenge was.
console.log(await page.title());
} else if (result.found === false) {
// found is nullable: false means no CAPTCHA, null means no value came back.
console.log("no captcha appeared");
} else {
console.error("solve failed:", result.error);
}
} finally {
// A rejected goto() or solve() would otherwise leave the session billing.
await browser.close();
}

Response when the solve succeeds:

Protected Page Title

Reading the result

solve() doesn't throw when a challenge can't be cleared. It reports through the response, so branch on the fields:

FieldMeaning
foundWhether a CAPTCHA was present at all. false with solved: false means the page wasn't challenged
solvedWhether the found CAPTCHA was cleared
tokenThe solve token, when the provider issues one you need to submit yourself
timeTotal milliseconds to find and solve
errorWhat went wrong, with guidance on fixing it

Supported types

Auto-detection covers the common providers, including Cloudflare, reCAPTCHA v2 and v3, GeeTest, DataDome, and Akamai. When targeting explicitly, type accepts any value of the schema-generated CaptchaType enum. Prefer auto-detect unless a site stacks multiple widgets and you need to pick one.

Waiting behavior

wait defaults to true, so solve() blocks until a CAPTCHA shows up or timeout elapses. That makes the click-then-solve pattern safe: trigger the action that provokes the challenge, then call solve(), and it catches the CAPTCHA whenever it renders. Pass wait: false to check for an already-visible challenge without blocking, which suits polling loops that mix solving with other work.

Solve an image CAPTCHA

Forms that show a distorted image next to a text input need solveImageCaptcha(). You point it at both elements and it fills the input with the solved value.

await page.solveImageCaptcha({
captchaSelector: "#captcha-image",
inputSelector: "#captcha-answer",
timeout: 30_000,
});

await page.click("#submit");

Both selectors are required. timeout covers the whole operation here, appearance and solve together, unlike solve() where it only bounds the wait for the challenge to appear.

Avoid CAPTCHAs before solving them

Solving costs time and units, so treat it as the last layer, not the first. Sessions on the stealth endpoint with a residential proxy get challenged far less than plain Chromium, and humanized typing keeps a cleared session from being re-challenged mid-form. The cheapest CAPTCHA is the one that never renders.

FAQ & Troubleshooting

solve() returns found: false but I can see the CAPTCHA

The challenge is probably inside a frame or shadow root that rendered after your call, or it's a provider auto-detection missed. Raise timeout so detection has longer to catch a slow-rendering widget, or pass the provider explicitly with type.

The CAPTCHA was solved but the page still blocks me

Some providers bind the solve to browsing behavior after the challenge. Keep the session on the stealth endpoint, keep the same proxy IP with sticky: true, and interact with default humanization rather than jumping straight to a protected URL.

Do failed solves throw an exception?

No. solve() reports through solved, found, and error on the response, so check those fields. Only transport failures and timeouts raise, as covered in error handling.

What do I do with the token field?

Usually nothing: the solve is applied inside the page. Providers like reCAPTCHA v3 hand back a token your own form submission may need to include, which is when you read it from the response and inject it yourself.

Next steps

Was this page helpful?