> ## 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.

# PDF Response

> Get a pixel-perfect PDF render of any scraped page using the `response_type=pdf` parameter.

The `response_type=pdf` parameter tells ZenRows to render the target page and return it as a PDF file instead of HTML. The result is a pixel-perfect snapshot of the page as it appears in a browser, including styles, images, and JavaScript-rendered content.

This is useful when you need to archive pages, generate reports, produce shareable snapshots, or feed visual page content into document processing pipelines.

<Warning>
  `response_type=pdf` cannot be combined with the `outputs` parameter. Use one or the other depending on whether you need a rendered PDF or targeted data extraction.
</Warning>

## How it works

PDF generation always requires a full browser render. When you add `response_type=pdf` to your request, ZenRows automatically enables JavaScript rendering and captures the fully rendered page as a PDF. You must include `js_render=true` explicitly in your request for the PDF response to work.

<Tip>Find more details on our [JS Rendering Documentation](/universal-scraper-api/features/js-rendering).</Tip>

The returned binary content is a standard PDF file that you can save to disk, upload to storage, or pass directly to a document processing tool.

## Basic usage

Add `response_type=pdf` and `js_render=true` to your request parameters:

<CodeGroup>
  ```python Python theme={null}
  import requests

  url = "https://www.scrapingcourse.com/ecommerce/"
  apikey = "YOUR_ZENROWS_API_KEY"

  params = {
      "url": url,
      "apikey": apikey,
      "js_render": "true",
      "response_type": "pdf",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params)
  print(response.text)
  ```

  ```javascript Node.js theme={null}
  import axios from "axios";

  const url = "https://www.scrapingcourse.com/ecommerce/";
  const apikey = "YOUR_ZENROWS_API_KEY";

  const response = await axios.get("https://api.zenrows.com/v1/", {
    params: {
      url,
      apikey,
      js_render: "true",
      response_type: "pdf",
    },
  });

  console.log(response.data);
  ```

  ```java Java theme={null}
  import org.apache.hc.client5.http.fluent.Request;

  public class APIRequest {
      public static void main(final String... args) throws Exception {
          String apiUrl = "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&response_type=pdf";
          String response = Request.get(apiUrl)
                  .execute().returnContent().asString();
          System.out.println(response);
      }
  }
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&response_type=pdf");
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $response = curl_exec($ch);
  echo $response . PHP_EOL;
  curl_close($ch);
  ?>
  ```

  ```go Go theme={null}
  package main

  import (
      "io"
      "log"
      "net/http"
  )

  func main() {
      client := &http.Client{}
      req, err := http.NewRequest("GET", "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&response_type=pdf", nil)
      if err != nil {
          log.Fatalln(err)
      }

      resp, err := client.Do(req)
      if err != nil {
          log.Fatalln(err)
      }
      defer resp.Body.Close()

      body, err := io.ReadAll(resp.Body)
      if err != nil {
          log.Fatalln(err)
      }

      log.Println(string(body))
  }
  ```

  ```ruby Ruby theme={null}
  require "faraday"

  url = URI.parse("https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&response_type=pdf")
  conn = Faraday.new()
  conn.options.timeout = 180
  res = conn.get(url, nil, nil)
  print(res.body)
  ```

  ```bash cURL theme={null}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&js_render=true&response_type=pdf"
  ```
</CodeGroup>

## Saving the PDF to disk

The API returns the PDF as binary content. Use `response.content` (not `response.text`) to write it correctly to a file:

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

url = "https://www.scrapingcourse.com/ecommerce/"
apikey = "YOUR_ZENROWS_API_KEY"

params = {
    "url": url,
    "apikey": apikey,
    "js_render": "true",
    "response_type": "pdf",
}

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

with open("output.pdf", "wb") as file:
    file.write(response.content)

print("PDF saved to output.pdf")
```

<Warning>
  Always write the response as binary (`"wb"` mode in Python). Writing it as text will corrupt the PDF file.
</Warning>

## When to use PDF response

PDF response is the right choice when you need a visual, printable, or archivable snapshot of a page. It works well for:

* **Page archiving**: capture a timestamped visual record of a page's content and layout.
* **Report generation**: convert data-rich pages, dashboards, or invoices into portable PDF documents.
* **Compliance and auditing**: preserve the rendered state of a page as it appeared at a specific point in time.
* **Document pipelines**: feed rendered page snapshots into PDF processing tools, OCR engines, or document management systems.
* **Sharing and distribution**: generate a shareable, print-ready version of a page without requiring the recipient to visit the URL.

<Note>If you need to extract text or structured data from the page rather than a visual snapshot, use `response_type=markdown`, `response_type=plaintext`, or the `outputs` parameter instead.</Note>

## Best practices

**Always include `js_render=true`:** <br />
PDF generation requires a full browser render. Requests without `js_render=true` will not return a valid PDF.

**Use `wait` or `wait_for` for pages with delayed content:** <br />
If the page loads content after the initial render (lazy-loaded images, deferred scripts), use `wait` (milliseconds) or `wait_for` (CSS selector) to ensure the full page is visible before the PDF is captured.

<Tip>Find more details on our [Wait Documentation](/universal-scraper-api/features/wait) or [Wait For Selector Documentation](/universal-scraper-api/features/wait-for).</Tip>

**Save as binary, not text:** <br />
PDF files are binary. Always write the response body in binary mode (for example, `"wb"` mode in Python and Ruby, `Buffer` in Node.js, `FileOutputStream` in Java). Writing binary content as text will produce a corrupt file.

**Credits are charged on successful responses:** <br />
A request using `response_type=pdf` is charged when the API returns a `200` status code, regardless of whether the PDF contains the content you expected. Because `js_render=true` is required, the JS rendering credit cost also applies. Test on a small set of URLs before running at scale.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The PDF is blank or missing content">
    The page likely loads content after the initial render. Add `wait` (in milliseconds) to give the page time to finish loading, or use `wait_for` with a CSS selector that is only present once the main content is visible. Also confirm that `js_render=true` is included in the request.
  </Accordion>

  <Accordion title="The saved PDF file is corrupted or unreadable">
    You are likely writing the response as text instead of binary. Always use binary write mode in your language of choice (`"wb"` in Python and Ruby, `Buffer` in Node.js, `FileOutputStream` in Java) and write the raw response body. PDF is a binary format and writing it as text will corrupt the file.
  </Accordion>

  <Accordion title="I'm getting an error when combining response_type=pdf with outputs">
    These two parameters are mutually exclusive. `response_type=pdf` returns a rendered PDF of the full page, while `outputs` performs targeted data extraction from the HTML. You can only use one per request.
  </Accordion>

  <Accordion title="The PDF does not include images or styles">
    This usually happens when external resources (stylesheets, images, fonts) fail to load during the render. Make sure the target URL is publicly accessible and that `js_render=true` is included. If the page uses resources that require authentication, those assets may not render correctly.
  </Accordion>
</AccordionGroup>

## Pricing

The `response_type=pdf` parameter is included at **no additional cost** beyond the standard JS rendering rate. Because PDF generation requires `js_render=true`, all PDF requests are billed at the JS rendering credit rate. You only pay extra for Premium Proxy when used.

<Tip>
  You can monitor your ZenRows usage in multiple ways to stay informed about your account activity and prevent unexpected overages.

  **Dashboard monitoring**: View real-time usage statistics, remaining requests, success rates, and request history on your [Analytics Page](https://app.zenrows.com/analytics/scraper-api). You can also set up usage alerts in your [notification settings](https://app.zenrows.com/account/notifications) to receive notifications when you approach your limits.

  **Programmatic monitoring**: For automated monitoring in your applications, call the `/v1/subscriptions/self/details` endpoint with your API key in the `X-API-Key` header. This returns real-time usage data that you can integrate into your monitoring systems. [Learn more about the usage endpoint](https://docs.zenrows.com/universal-scraper-api/features/other#plan-usage).

  **Response header monitoring**: Track your concurrency usage through response headers included with each request:

  * `Concurrency-Limit`: Your maximum concurrent requests
  * `Concurrency-Remaining`: Available concurrent request slots
  * `X-Request-Cost`: Cost of the current request
</Tip>

## FAQ (Frequently Asked Questions)

<Accordion title="Is js_render=true required for PDF response?">
  Yes. PDF generation requires a full browser render. You must include `js_render=true` in every request that uses `response_type=pdf`. Requests without it will not return a valid PDF.
</Accordion>

<Accordion title="Does response_type=pdf cost more credits?">
  PDF requests are billed at the JS rendering credit rate because `js_render=true` is required. The `response_type=pdf` parameter itself does not add an extra cost beyond that.
</Accordion>

<Accordion title="Can I use response_type=pdf with the Scraping Browser?">
  No, `response_type=pdf` is a parameter of the Universal Scraper API. It is not available in Scraping Browser (CDP/Playwright) sessions. In a Scraping Browser session, you can generate PDFs using Playwright's built-in `page.pdf()` method directly in your script.
</Accordion>

<Accordion title="Can I control the PDF page size or orientation?">
  Not currently. The PDF is generated using the browser's default page settings. If you need specific page dimensions or orientation, consider post-processing the PDF with a library like `pypdf` or `reportlab` after saving it.
</Accordion>

<Accordion title="Can I use response_type=pdf to capture paginated content?">
  The PDF captures a single page render. For paginated content, you would need to make a separate request for each URL or page state you want to capture, then combine the resulting PDFs if needed.
</Accordion>
