> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anchorbrowser.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Anchor Browser is cloud browser infrastructure for AI agents and automation: browser sessions you control over CDP or REST, natural-language web tasks, versioned automation tasks, managed authentication into third-party web apps (identities with MFA), persistent volumes and profiles, recordings, live view, batch sessions, webhooks, and network options including residential proxies, extra stealth, and captcha solving for sites that block standard browsers. AI agents should start at https://docs.anchorbrowser.io/for-agents, then get an API key with Agent Access at https://docs.anchorbrowser.io/quickstart/agent-access — no dashboard account required. Read those two pages before authentication or API calls.

# Web Unlocker

> Fetch fully rendered HTML from any URL — including CAPTCHA- and bot-protected sites — with a single API call. No browser session, no proxy setup, billed only on success.

Anchor Web Unlocker is a sessionless retrieval API. You send a URL; Anchor loads it in a stealth browser behind a residential proxy, solves any CAPTCHA or bot-detection challenge it encounters, and returns the rendered HTML. It is the fastest way to get page content from sites that block plain HTTP clients, and it requires none of the session lifecycle management that a full browser does.

<CardGroup cols={2}>
  <Card title="Automatic CAPTCHA solving" icon="shield-check">
    Cloudflare, reCAPTCHA, FunCaptcha, DataDome, Kasada and Akamai challenges are solved automatically. You receive the page behind the challenge, not the interstitial.
  </Card>

  <Card title="Residential proxies and stealth built in" icon="user-secret">
    Every request egresses through a residential IP with a randomized, consistent browser fingerprint. Nothing to configure.
  </Card>

  <Card title="Full JavaScript rendering" icon="code">
    Pages are loaded in real Chromium, so client-side rendered content, lazy-loaded sections and consent dialogs are handled before the HTML is captured.
  </Card>

  <Card title="Pay only for successful fetches" icon="coins">
    Only `200` responses are billed. Requests that fail with a `4xx` or `5xx` cost nothing.
  </Card>
</CardGroup>

## Quick start

<CodeGroup>
  ```javascript node.js theme={null}
  import { client, Tools } from 'anchorbrowser';

  client.setConfig({ auth: () => process.env.ANCHOR_API_KEY });

  const html = await Tools.fetchWebpage({
    body: {
      url: 'https://www.g2.com/products/notion/reviews',
    },
  });

  console.log(html);
  ```

  ```python python theme={null}
  import os
  from anchorbrowser import Anchorbrowser

  anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY'))

  html = anchor_client.tools.fetch_webpage(
      url='https://www.g2.com/products/notion/reviews',
  )

  print(html)
  ```

  ```bash curl theme={null}
  curl --request POST \
    --url https://api.anchorbrowser.io/v1/tools/fetch/webpage \
    --header "anchor-api-key: $ANCHOR_API_KEY" \
    --header 'Content-Type: application/json' \
    --data '{"url": "https://www.g2.com/products/notion/reviews"}'
  ```
</CodeGroup>

<Tip>
  Autonomous agents can obtain an API key without a dashboard account through [Agent Access](/quickstart/agent-access), then call Web Unlocker directly.
</Tip>

## What Web Unlocker handles for you

| Obstacle | Result |
| - | - |
| Bot detection and IP reputation | Requests arrive from residential IPs with a real browser fingerprint. |
| CAPTCHAs and challenge pages | Cloudflare, reCAPTCHA, FunCaptcha, DataDome, Kasada and Akamai challenges are solved automatically. You receive the page behind the challenge, never the interstitial. |
| Client-side rendering | JavaScript runs to completion in real Chromium, so SPAs, lazy-loaded sections and hydrated content are present in the HTML. |
| Cookie-consent banners | Consent dialogs are dismissed before the page is captured. |
| Non-HTML targets | PDFs, file downloads, JSON and XML are returned as-is with their original content type. |
| Infrastructure | No browser to start, connect to, or close. Requests do not count against your concurrent-browser limit. |

You send a URL and receive the rendered HTML. Everything in the table above happens inside the request.

## API reference

**Endpoint:** `POST https://api.anchorbrowser.io/v1/tools/fetch/webpage`

**Headers**

| Header | Value |
| - | - |
| `anchor-api-key` | Your API key |
| `Content-Type` | `application/json` |

**Body**

<ParamField body="url" type="string" required>
  The fully-qualified URL to fetch, including the scheme (e.g. `https://www.linkedin.com/company/openai`).
</ParamField>

<ParamField body="options" type="object">
  Optional retrieval settings.

  <Expandable title="properties">
    <ParamField body="return_partial_on_timeout" type="boolean" default="true">
      When a page is still loading at the deadline, return the content rendered so far instead of failing. Set to `false` to receive a `504` instead of partial content.
    </ParamField>
  </Expandable>
</ParamField>

**Response**

A successful request returns `200` with the page body directly — not wrapped in JSON.

| Target | `Content-Type` | Body |
| - | - | - |
| HTML page | `text/html; charset=utf-8` | Rendered HTML after JavaScript execution |
| PDF | `application/pdf` | The file bytes, with `Content-Disposition: attachment` |
| File download (CSV, XLSX, ZIP, …) | `text/csv` or `application/octet-stream` | The file bytes, with `Content-Disposition: attachment` |
| JSON, XML or plain-text resource | The upstream content type | The response body unchanged |

**Error codes**

Errors are returned as JSON: `{ "error": "<message>" }`. The exception is `429`, which uses the platform-wide shape `{ "error": { "code": 429, "message": "<message>" } }`.

| Code | Meaning | Retry? |
| - | - | - |
| `400` | Invalid request — check the URL | No |
| `402` | Insufficient credits balance | After topping up |
| `403` | Project cost limit reached | After raising the limit |
| `422` | Could not reach the URL, DNS failed, or the challenge could not be solved | Once, after a short delay |
| `429` | Rate limit exceeded | With exponential backoff |
| `500` | Failed to fetch the requested page | With exponential backoff |
| `503` | Service temporarily unavailable | With exponential backoff |
| `504` | The page took too long to load | Once, or enable `return_partial_on_timeout` |

## Limits and timeouts

| | Value |
| - | - |
| Rate limit | 2,000 requests per minute per API key by default. Higher limits are available on request. |
| Concurrency | Not tied to your plan's concurrent-browser limit. |
| Request duration | Typically a few seconds. Pages that present a CAPTCHA take longer because the challenge is solved before the response is returned. Requests are capped at about two minutes; configure client timeouts of at least 120 seconds. |
| Proxy geography | Residential, automatically selected. For a specific country or a sticky IP, use a [browser session with proxy configuration](/advanced/proxy). |

## Pricing

Web Unlocker is billed per `200` response: **0.005 credits per fetch**, or \*\*$0.005** at the standard $1 per credit. 1,000 pages cost \$5. Requests that fail with a `4xx` or `5xx` are not billed, and no browser-hour or proxy-bandwidth charges apply. See [Pricing](/pricing) for plan details and credit rates.

## Examples

### Fetch a bot-protected page

<CodeGroup>
  ```javascript node.js theme={null}
  import { client, Tools } from 'anchorbrowser';

  client.setConfig({ auth: () => process.env.ANCHOR_API_KEY });

  const html = await Tools.fetchWebpage({
    body: {
      url: 'https://www.indeed.com/jobs?q=software+engineer&l=New+York',
    },
  });

  console.log(html);
  ```

  ```python python theme={null}
  import os
  from anchorbrowser import Anchorbrowser

  anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY'))

  html = anchor_client.tools.fetch_webpage(
      url='https://www.indeed.com/jobs?q=software+engineer&l=New+York',
  )

  print(html)
  ```
</CodeGroup>

### Fetch many pages in parallel

Requests are independent, so fan them out. Keep total throughput under your per-minute rate limit and back off on `429`.

<CodeGroup>
  ```javascript node.js theme={null}
  import { client, Tools } from 'anchorbrowser';

  client.setConfig({ auth: () => process.env.ANCHOR_API_KEY });

  const urls = [
    'https://www.crunchbase.com/organization/openai',
    'https://www.crunchbase.com/organization/anthropic',
    'https://www.crunchbase.com/organization/mistral-ai',
  ];

  const pages = await Promise.all(
    urls.map((url) => Tools.fetchWebpage({ body: { url } })),
  );

  pages.forEach((html, index) => {
    console.log(urls[index], html.slice(0, 200));
  });
  ```

  ```python python theme={null}
  import os
  from concurrent.futures import ThreadPoolExecutor
  from anchorbrowser import Anchorbrowser

  anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY'))

  urls = [
      'https://www.crunchbase.com/organization/openai',
      'https://www.crunchbase.com/organization/anthropic',
      'https://www.crunchbase.com/organization/mistral-ai',
  ]

  with ThreadPoolExecutor(max_workers=10) as pool:
      pages = list(pool.map(lambda url: anchor_client.tools.fetch_webpage(url=url), urls))

  for url, html in zip(urls, pages):
      print(url, html[:200])
  ```
</CodeGroup>

### Call the HTTP API directly with retries

Read the body as text on success; the endpoint returns HTML, not a JSON envelope. Retry `429`, `500`, `503` and `504` with exponential backoff and treat everything else as terminal.

<CodeGroup>
  ```javascript node.js theme={null}
  const RETRYABLE = new Set([429, 500, 503, 504]);

  async function unlock(url, { attempts = 3 } = {}) {
    for (let attempt = 1; attempt <= attempts; attempt++) {
      const response = await fetch('https://api.anchorbrowser.io/v1/tools/fetch/webpage', {
        method: 'POST',
        headers: {
          'anchor-api-key': process.env.ANCHOR_API_KEY,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ url }),
        signal: AbortSignal.timeout(120_000),
      });

      if (response.ok) {
        return response.text();
      }

      const { error } = await response.json().catch(() => ({ error: response.statusText }));
      const message = typeof error === 'string' ? error : error?.message ?? response.statusText;
      if (!RETRYABLE.has(response.status) || attempt === attempts) {
        throw new Error(`Web Unlocker ${response.status}: ${message}`);
      }
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
    }
  }

  const html = await unlock('https://example.com');
  ```

  ```python python theme={null}
  import os
  import time
  import requests

  RETRYABLE = {429, 500, 503, 504}


  def unlock(url: str, attempts: int = 3) -> str:
      for attempt in range(1, attempts + 1):
          response = requests.post(
              'https://api.anchorbrowser.io/v1/tools/fetch/webpage',
              headers={
                  'anchor-api-key': os.environ['ANCHOR_API_KEY'],
                  'Content-Type': 'application/json',
              },
              json={'url': url},
              timeout=120,
          )
          if response.ok:
              return response.text

          try:
              error = response.json().get('error', response.reason)
              message = error if isinstance(error, str) else error.get('message', response.reason)
          except ValueError:
              message = response.reason
          if response.status_code not in RETRYABLE or attempt == attempts:
              raise RuntimeError(f'Web Unlocker {response.status_code}: {message}')
          time.sleep(2 ** (attempt - 1))


  html = unlock('https://example.com')
  ```
</CodeGroup>

### Fail fast instead of returning partial content

By default, a page that is still loading at the deadline is returned with whatever has rendered so far. When you need complete pages or nothing, disable partial returns and handle the `504`.

```bash curl theme={null}
curl --request POST \
  --url https://api.anchorbrowser.io/v1/tools/fetch/webpage \
  --header "anchor-api-key: $ANCHOR_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "url": "https://example.com/heavy-page",
    "options": { "return_partial_on_timeout": false }
  }'
```

### Download a PDF

PDF URLs are returned as the file itself. Save the response body to disk rather than reading it as text.

<CodeGroup>
  ```javascript node.js theme={null}
  import { writeFile } from 'node:fs/promises';

  const response = await fetch('https://api.anchorbrowser.io/v1/tools/fetch/webpage', {
    method: 'POST',
    headers: {
      'anchor-api-key': process.env.ANCHOR_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url: 'https://arxiv.org/pdf/1706.03762' }),
  });

  if (!response.ok) {
    throw new Error(`Web Unlocker ${response.status}: ${await response.text()}`);
  }

  await writeFile('attention.pdf', Buffer.from(await response.arrayBuffer()));
  ```

  ```python python theme={null}
  import os
  import requests

  response = requests.post(
      'https://api.anchorbrowser.io/v1/tools/fetch/webpage',
      headers={'anchor-api-key': os.environ['ANCHOR_API_KEY']},
      json={'url': 'https://arxiv.org/pdf/1706.03762'},
      timeout=120,
  )
  response.raise_for_status()

  with open('attention.pdf', 'wb') as f:
      f.write(response.content)
  ```
</CodeGroup>

## Choosing the right tool

| | Web Unlocker | Browser session | Anchor proxy | AI task (`perform-web-task`) |
| - | - | - | - | - |
| Input | One URL | Session + CDP / Playwright / Puppeteer | Your own HTTP client or browser | Natural-language goal |
| Output | Rendered HTML (or file) | Full interactive browser | Network egress only | Structured result |
| CAPTCHA solving | Automatic | Optional, [configurable](/advanced/captcha-solving) | None | Automatic when enabled |
| Setup | None | Create and close a session | Configure proxy credentials | None |
| State across requests | None | Cookies, logins, tabs persist | None | Within the task |
| Cold start | None | Session startup time | None | Session startup time |
| Billing | Per successful request | Per session and browser-hour | Per GB | Per task and AI step |
| Best for | High-volume scraping, one-shot fetches, RAG ingestion, monitoring | Logins, forms, multi-step flows, anything that needs clicks | Adding residential IPs to an existing stack | Goal-driven automation without writing selectors |

Use **Web Unlocker** when the deliverable is the content of a URL. Use a [**browser session**](/quickstart/browser-sessions) when the workflow has to navigate, click, submit forms, authenticate, or keep state between steps. Use the [**Anchor proxy**](/proxy-integration/quickstart) when you already run your own browser fleet and only need residential routing. Use an [**AI task**](/agentic-browser-control/ai-task-completion) when you want to describe the outcome rather than script the steps.

These compose well: fetch listing pages in bulk with Web Unlocker, then open a session only for the pages that require an authenticated action.

## FAQ

<AccordionGroup>
  <Accordion title="Does Web Unlocker solve CAPTCHAs?">
    Yes. Cloudflare, reCAPTCHA, FunCaptcha, DataDome, Kasada and Akamai challenges are solved automatically and you receive the page behind them. If a challenge is detected but cannot be solved, the request fails with `422` and is not billed.
  </Accordion>

  <Accordion title="Does it execute JavaScript?">
    Yes. Pages are rendered in real Chromium. The HTML you receive reflects the DOM after client-side rendering, not the initial server response.
  </Accordion>

  <Accordion title="Do I need to create a browser session first?">
    No. Web Unlocker is sessionless. There is no browser to create, connect to, or close, no cold start, and requests do not count against your concurrent-browser limit.
  </Accordion>

  <Accordion title="Can I choose the proxy country or use a sticky IP?">
    Not on this endpoint — proxies are residential and selected automatically. For country targeting, sticky IPs, or your own proxy, use a [browser session with proxy configuration](/advanced/proxy).
  </Accordion>

  <Accordion title="Am I charged for failed requests?">
    Only `200` responses are billed; any `4xx` or `5xx` is free. Unreachable hosts (`422`), unsolved challenges (`422`), rate limits (`429`) and timeouts (`504`) therefore cost nothing.
  </Accordion>

  <Accordion title="Can I get Markdown or structured data instead of HTML?">
    Web Unlocker returns raw HTML so you can run your own extraction. For LLM-ready Markdown or structured extraction from a rendered page, use the [session tools](/essentials/control-a-session) or an [AI task](/agentic-browser-control/ai-task-completion).
  </Accordion>

  <Accordion title="What are the acceptable-use requirements?">
    Use Web Unlocker only for websites and data you are authorized to access, and in compliance with the target site's terms and applicable law. See [Security](/security) for Anchor's compliance posture.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.