On this page

    Get the right capture

    Capture Sites with Bot Protection

    When a site shows Access denied or a challenge instead of content: stop paying for those images, then let the renderer through on sites you own.

    Time
    15 minutes
    Plan
    Free
    You need
    • An API key in RS_API_KEY
    • For step 3, access to the site's server or bot protection settings

    What you will have at the end

    This is what bot protection looks like in a screenshot. Steps 1 and 2 make it fail at no cost; step 3 lets the renderer through.

    Some sites decide that the renderer is a bot and show a challenge page instead of their content. They are right: it is a browser run by software. You will make those captures fail so they cost nothing, and, for sites you own or run for a client, let the renderer through.

    RenderScreenshot does not solve CAPTCHAs and does not disguise the browser. The site owner decides who gets in. A custom user agent does not change that.

    Recognise a blocked capture

    The image at the top of this guide is a real capture: the request succeeded, so it used a credit, but the image shows a block message. Signs to look for:

    You get Meaning Credit used
    An image of "Access denied", "Verify you are human" or a blank challenge The browser loaded the challenge page Yes
    Error blocked (422) The site refused the request No
    Error timeout (422) The challenge never finished No

    Retrying does not help: the next attempt is a new browser with the same signals.

    Make blocked captures fail for free

    Turn the first row into an error. A failed request uses no credits and leaves no useless image in your storage.

    Check the text. List the phrases of the block pages you get. The request fails when the page contains one of them:

    {
      "url": "https://www.reddit.com",
      "options": {
        "fail_if_contains": ["blocked by network security", "Access denied", "Verify you are human"]
      }
    }
    

    The real response for the page above:

    {
      "error": {
        "type": "target_error",
        "code": "content_check_failed",
        "message": "Forbidden text found: 'blocked by network security'",
        "retryable": true
      },
      "request_id": "req_a53b5f66a8d44ca2"
    }
    

    Matching is case-sensitive, so copy the phrases exactly. fail_if_missing does the opposite: the request fails when the page does not contain a phrase, for example ["Add to cart"] on a product page.

    Or wait for real content. Wait for an element that only the real page has. A challenge page never shows it, so the request fails after the timeout:

    {
      "url": "https://shop.example.com/product/42",
      "wait": { "for_selector": "[data-product-id]", "timeout": 20 }
    }
    

    In your code, treat content_check_failed and blocked as "do not retry", even though retryable is true. One retry at most.

    Let the renderer through on your own site

    If the site is yours, or you capture it for its owner, pick one of these. They go from the most specific to the broadest.

    Option A: send your own HTML. For images of your own content (cards, reports, invoices), render the HTML in your app and send it with html. No request reaches your bot protection:

    {
      "html": "<html><body><h1>Invoice #1042</h1>...</body></html>",
      "viewport": { "width": 1200, "height": 630 }
    }
    

    Option B: a secret header. Send a long random token in a header, and skip your bot check when it matches:

    {
      "url": "https://www.example.com/pricing",
      "network": { "headers": { "X-Screenshot-Token": "<32+ random characters>" } }
    }
    

    On your server or edge function, compare it in constant time:

    import { timingSafeEqual } from 'node:crypto';
    
    const expected = Buffer.from(process.env.SCREENSHOT_BYPASS_TOKEN ?? '');
    
    export function isTrustedScreenshot(req) {
      if (expected.length < 32) return false;
      const got = Buffer.from(String(req.headers['x-screenshot-token'] ?? ''));
      return got.length === expected.length && timingSafeEqual(got, expected);
    }
    

    Give the token no power except skipping the challenge, and rotate it.

    Option C: a rule in your bot or firewall product. Most products can skip challenges for traffic that matches a rule. For sites behind Cloudflare, the Cloudflare guide shows the exact rule for each plan. Do not allowlist IP addresses: we do not publish a fixed range.

    Next steps

    Try RenderScreenshot free

    150 free credits every month, no credit card required.