Stealth mode
Capture sites protected by Cloudflare, DataDome, and other bot detection
Stealth mode
Capture screenshots of sites that block ordinary automated browsers — pages behind Cloudflare, DataDome, PerimeterX, and similar bot protection that normally return a challenge page, an "access denied" wall, or a captcha instead of the content you asked for.
Stealth mode is included on Pro and up — and available on Starter for a limited time as an introduction offer (up to 100 stealth captures a month). Every capture is slower and more resource-intensive than a standard one, so turn it on only for sites that block standard captures — leave it off everywhere else.
When to use it
Reach for stealth mode when a normal capture comes back as something other than the page you wanted:
- A Cloudflare interstitial — "Checking your browser before you continue…"
- A DataDome, PerimeterX, or similar bot-protection block page
- An "Access denied" / "You don't have permission" wall
- A captcha (reCAPTCHA, hCaptcha, Turnstile)
- A near-empty or stripped-down page on a site that loads fine in your own browser
Those are signs the site is rejecting the request based on the client, not a problem with your parameters. If a standard capture already returns the right page, you don't need stealth mode — and shouldn't pay for it.
Enabling it
Add stealthMode: true to any capture request:
{
"url": "https://heavily-protected-site.com",
"stealthMode": true
}curl -X POST 'https://api.allscreenshots.com/v1/screenshots' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://heavily-protected-site.com",
"stealthMode": true
}' --output capture.pngThe flag works the same way on the async, bulk, and scheduled endpoints — set stealthMode: true in the options.
How it works
When stealthMode is on, the capture runs through a path built to look like a real person on a real browser rather than an automated one. At a high level it:
- Routes the request through residential IP addresses, so it doesn't arrive from an obvious datacenter range.
- Presents a hardened, realistic browser fingerprint.
- Interacts with the page the way a person would before capturing.
- Verifies it captured the real page, not a block, and recovers on its own when a site pushes back — so you don't get a screenshot of a challenge wall.
That's all you need to manage — the single stealthMode flag. We intentionally don't publish the exact techniques: detection vendors adapt to anything documented, so the specifics stay private and change over time.
What to expect
- It's slower. A stealth capture does more work and may retry, so it takes longer than a standard one. Give it a generous
timeout. - It's not a guarantee. Stealth mode dramatically raises the success rate on protected sites, but a site sitting behind an unsolved interactive captcha can still refuse the capture. When that happens you get a clear error (below) rather than a screenshot of the wall.
- Ad and cookie blocking still apply, so a successful stealth capture is just as clean as a standard one. See Blocking.
Errors
If a stealth capture can't succeed, the API returns a specific error instead of a screenshot of a block page:
| Status | Code | Meaning | What to do |
|---|---|---|---|
403 | CAPABILITY_NOT_AVAILABLE | Your plan doesn't include stealth mode | Upgrade to Pro (or grab the limited-time Starter offer), or drop stealthMode |
402 | STEALTH_PROMO_LIMIT_REACHED | You've used this month's Starter intro-offer stealth allowance | Upgrade to Pro for unlimited stealth, or wait for next month's reset |
422 | TARGET_BLOCKED | The site served a bot-protection block or captcha that we couldn't get past | Retry later; some sites can't be captured |
422 | STEALTH_CAPTURE_FAILED | The stealth capture failed for another reason | Check the reason in the response; retry |
502 | STEALTH_UNAVAILABLE | Stealth capture is temporarily unavailable | Retry shortly |
503 | SERVICE_BUSY | Workers are busy | Retry after a short delay |
TARGET_BLOCKED means the page was a wall, not your screenshot. It's worth a retry — protected sites are inconsistent — but treat repeated blocks on the same URL as "this site can't be captured right now" rather than a transient error.
Best practices
Use it selectively
Stealth mode is the exception, not the default. Capture normally first, and switch on stealthMode only for the specific URLs that come back blocked. Sending every request through stealth mode is slower and burns the Pro feature on sites that never needed it.
Give it more time
Because stealth captures can retry, raise the timeout for protected sites:
{
"url": "https://heavily-protected-site.com",
"stealthMode": true,
"timeout": 45000,
"waitUntil": "networkidle"
}Handle blocks in code
Treat 422 and 503 as retryable. A small backoff and one or two retries clears most transient blocks:
async function captureWithStealth(url, attempts = 3) {
for (let i = 0; i < attempts; i++) {
const res = await fetch('https://api.allscreenshots.com/v1/screenshots', {
method: 'POST',
headers: { 'X-API-Key': process.env.API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ url, stealthMode: true, timeout: 45000 }),
});
if (res.ok) return Buffer.from(await res.arrayBuffer());
if (res.status === 422 || res.status === 503) {
await new Promise((r) => setTimeout(r, 2000 * (i + 1)));
continue;
}
throw new Error(`Capture failed: ${res.status}`);
}
throw new Error('Site stayed blocked after retries');
}Examples
Cloudflare-protected page
curl -X POST 'https://api.allscreenshots.com/v1/screenshots' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/protected",
"stealthMode": true,
"fullPage": true,
"timeout": 45000
}' --output protected.pngWait for content after the challenge clears
curl -X POST 'https://api.allscreenshots.com/v1/screenshots' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/listings",
"stealthMode": true,
"waitFor": ".results-loaded",
"waitUntil": "networkidle",
"timeout": 50000
}' --output listings.pngScheduled stealth capture
curl -X POST 'https://api.allscreenshots.com/v1/schedules' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"name": "Protected competitor page",
"url": "https://competitor.com/pricing",
"schedule": "daily",
"options": { "stealthMode": true, "fullPage": true }
}'