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

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Capture screenshots, PDFs, and files

> Capture full-page or element screenshots, save pages as PDF and download files with Zenrows Fetch.

Fetch can hand back a picture of the page instead of its HTML: a screenshot of the viewport, the full page or one element, or a PDF of the rendered page. Point it at a file URL and it returns the file itself. Each one is binary content you write straight to disk.

**Uses:** [Fetch](/fetch/api-reference) with [`screenshot`](/fetch/features/screenshot), [`response_type=pdf`](/fetch/features/pdf) and [file downloads](/fetch/features/downloadfile)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use Zenrows to take a full-page screenshot of https://www.scrapingcourse.com/ecommerce/
and describe the layout of the product grid.
```

## Take a full-page screenshot

Set `js_render=true`, `screenshot=true` and `screenshot_fullpage=true`. The response body is a PNG.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.scrapingcourse.com/ecommerce/",
      "js_render": "true",
      "screenshot": "true",
      "screenshot_fullpage": "true",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()

  with open("page.png", "wb") as f:
      f.write(response.content)

  print(len(response.content), "bytes saved to page.png")
  ```

  ```javascript Node.js theme={"dark"}
  import { writeFile } from "node:fs/promises";

  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://www.scrapingcourse.com/ecommerce/",
    js_render: "true",
    screenshot: "true",
    screenshot_fullpage: "true",
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const image = Buffer.from(await response.arrayBuffer());
  await writeFile("page.png", image);
  console.log(image.length, "bytes saved to page.png");
  ```

  ```bash cURL theme={"dark"}
  curl -o page.png "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&screenshot=true&screenshot_fullpage=true"
  ```
</CodeGroup>

From the CLI, `--screenshot` saves the visible area:

```bash CLI theme={"dark"}
zenrows fetch https://www.scrapingcourse.com/ecommerce/ --screenshot --out page.png
```

## Example output

```text theme={"dark"}
531780 bytes saved to page.png
```

`page.png` is a PNG of the whole shop page, from the header to the footer.

## Capture one element

Set `screenshot_selector` to a CSS selector, together with `screenshot=true`, and the image is cropped to that element. `screenshot_format=jpeg` with `screenshot_quality` keeps files small.

```python Python (Requests) theme={"dark"}
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/",
    "js_render": "true",
    "screenshot": "true",
    "screenshot_selector": ".product .summary",
    "screenshot_format": "jpeg",
    "screenshot_quality": "80",
}

response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()

with open("product.jpg", "wb") as f:
    f.write(response.content)

print(len(response.content), "bytes saved to product.jpg")
```

Output: `50832 bytes saved to product.jpg`, an 832 x 1030 image of the product title, price, options and Add to cart button.

## Save the page as a PDF

Set `js_render=true` and `response_type=pdf`. The body is a PDF of the rendered page, ready to archive or pass to a document pipeline.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/",
      "js_render": "true",
      "response_type": "pdf",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()

  with open("product.pdf", "wb") as f:
      f.write(response.content)

  print(len(response.content), "bytes saved to product.pdf")
  ```

  ```bash CLI theme={"dark"}
  zenrows fetch https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/ --manual --js-render --output pdf --out product.pdf
  ```
</CodeGroup>

Output: `190406 bytes saved to product.pdf`, an 8-page PDF.

## Download a file directly

When the URL points at a file, such as an image, a PDF or a CSV, Fetch returns the file itself. No extra parameters are needed.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()

  with open("dummy.pdf", "wb") as f:
      f.write(response.content)

  print(len(response.content), "bytes saved to dummy.pdf")
  print("Credits billed:", response.headers["X-Request-Credits"])
  ```

  ```javascript Node.js theme={"dark"}
  import { writeFile } from "node:fs/promises";

  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const file = Buffer.from(await response.arrayBuffer());
  await writeFile("dummy.pdf", file);
  console.log(file.length, "bytes saved to dummy.pdf");
  console.log("Credits billed:", response.headers.get("X-Request-Credits"));
  ```
</CodeGroup>

Both print the same:

```text theme={"dark"}
13264 bytes saved to dummy.pdf
Credits billed: 1
```

## Customize it

| Change | How |
| - | - |
| The visible area | `screenshot=true` without `screenshot_fullpage`, see [Capture mode](/fetch/features/screenshot#capture-mode) |
| Smaller images | `screenshot_format=jpeg` and `screenshot_quality=1` to `100`, see [Image format and quality](/fetch/features/screenshot#image-format-and-quality) |
| Screenshot plus HTML in one response | Add `json_response=true`: the image comes back base64-encoded next to the HTML, see [JSON Response](/fetch/features/json-response) |
| Remove a cookie banner before capture | Add `js_instructions` with `evaluate`, see [Click, fill, and scroll before you scrape](/academy/fetch/page-interactions) |
| Protected site | Add `premium_proxy=true`, see [Scrape pages protected by anti-bot systems](/academy/fetch/protected-sites) |
| Screenshots inside a multi-step browser flow | Use Browser, see [Take screenshots and save pages as PDF](/academy/browser/screenshots-and-pdfs) |

## Troubleshooting

* **The saved file is HTML, not an image**: the request had no `screenshot=true`. Send it together with `screenshot_fullpage` or `screenshot_selector`, and with `js_render=true`.
* **`400` with code `REQS004` on a PDF request**: `response_type=pdf` renders the page. Add `js_render=true`, or `--manual --js-render` in the CLI.
* **The image opens as a broken file**: the response was an error. Check the status code before writing the body, as the samples do with `raise_for_status()` and `response.ok`.
* **The element screenshot is empty or wrong**: the selector matched a different or hidden element. Test the selector in your browser's console with `document.querySelector`.
* **Content is missing from the capture**: it loads late. Add `wait_for` with a selector of that content.

## Related recipes

<CardGroup cols={2}>
  <Card title="Click, fill, and scroll before you scrape" icon="click" href="/academy/fetch/page-interactions">
    Act on the page before the capture.
  </Card>

  <Card title="Take screenshots and save pages as PDF" icon="photo" href="/academy/browser/screenshots-and-pdfs">
    Capture any step of a multi-page browser session.
  </Card>

  <Card title="Scrape JavaScript-rendered pages" icon="brand-javascript" href="/academy/fetch/javascript-pages">
    Wait for the content before you capture it.
  </Card>

  <Card title="Detect changes on any page and get alerted" icon="eye" href="/academy/pipelines/change-detection">
    Compare pages over time and act on the difference.
  </Card>
</CardGroup>


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