ScreenshotNeo

BlogHTML to image & PDF

How to Convert an HTML Page to PDF with ScreenshotAPI.net

Convert a public URL or raw HTML to PDF with ScreenshotAPI.net. Choose page size, orientation, and rendering options, then save the response safely.

By the ScreenshotNeo team4 October 202610 min read

To convert a public web page or raw HTML into a PDF with ScreenshotAPI.net, call its v3 screenshot endpoint with your API token and file_type=pdf. Pass a reachable page as url, or send markup as custom_html. Choose a paper format such as A4 if you need page breaks; with no format or dimensions, the documentation says the output is one continuous page. See the PDF rendering documentation and rendering endpoint documentation.

1. Choose the input: URL or HTML

The endpoint is GET https://shot.screenshotapi.net/v3/screenshot. Every request needs token and file_type=pdf.

  • Public or externally reachable page: pass its full address in url. The rendering service must be able to reach the page. A URL behind a login, firewall, or bot check may need access configuration; do not assume it will render successfully without it.
  • Markup you already have: send it in custom_html. When this parameter is present, it takes precedence over url. Inline CSS and JavaScript in the supplied markup are supported by the documentation, but remote assets still need to be reachable.

Get the token from your ScreenshotAPI dashboard. Keep it private: do not put it in browser-side code, a public repository, or logs. The examples below use an environment variable named SCREENSHOTAPI_TOKEN.

2. Convert a URL to PDF

This cURL example requests an A4 portrait PDF with print media and printed backgrounds, then saves the response as a PDF file. cURL encodes the query values so the target URL and bracketed option names are transmitted safely.

export SCREENSHOTAPI_TOKEN='YOUR_TOKEN'
curl --fail --silent --show-error --get \
  'https://shot.screenshotapi.net/v3/screenshot' \
  --data-urlencode "token=$SCREENSHOTAPI_TOKEN" \
  --data-urlencode 'url=https://example.com/' \
  --data-urlencode 'file_type=pdf' \
  --data-urlencode 'pdf_options[format]=A4' \
  --data-urlencode 'pdf_options[media]=print' \
  --data-urlencode 'pdf_options[print_background]=true' \
  --output page.pdf

Use your actual page address in place of https://example.com/. The PDF options use the documented pdf_options group. Keep query parameters URL-encoded; raw HTML and CSS can contain characters that otherwise break a URL.

Python with requests

import os
import requests

api_url = "https://shot.screenshotapi.net/v3/screenshot"
params = {
    "token": os.environ["SCREENSHOTAPI_TOKEN"],
    "url": "https://example.com/",
    "file_type": "pdf",
    "pdf_options[format]": "A4",
    "pdf_options[media]": "print",
    "pdf_options[print_background]": "true",
}

response = requests.get(api_url, params=params, timeout=120)
response.raise_for_status()

content_type = response.headers.get("Content-Type", "")
if "pdf" not in content_type.lower():
    raise RuntimeError(f"Expected a PDF response, received Content-Type: {content_type}")

with open("page.pdf", "wb") as pdf_file:
    pdf_file.write(response.content)

Install the dependency with python -m pip install requests. The explicit content-type check can catch cases where an error response is returned instead of the expected document.

Node.js with fetch

import { writeFile } from "node:fs/promises";

const params = new URLSearchParams({
  token: process.env.SCREENSHOTAPI_TOKEN,
  url: "https://example.com/",
  file_type: "pdf",
  "pdf_options[format]": "A4",
  "pdf_options[media]": "print",
  "pdf_options[print_background]": "true",
});

const response = await fetch(
  `https://shot.screenshotapi.net/v3/screenshot?${params}`,
  { signal: AbortSignal.timeout(120_000) }
);

if (!response.ok) {
  throw new Error(`ScreenshotAPI returned HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.toLowerCase().includes("pdf")) {
  throw new Error(`Expected a PDF response, got ${contentType}`);
}
await writeFile("page.pdf", Buffer.from(await response.arrayBuffer()));

Run it in a Node.js version with built-in fetch, after setting SCREENSHOTAPI_TOKEN in the process environment. Treat the response as binary data; converting it to text can corrupt the file.

3. Convert raw HTML to PDF

For small HTML documents, custom_html can be sent as a query parameter. For substantial markup, use POST: the documentation recommends it because GET requests have URL length limits. The documentation describes the parameter and recommends POST for larger payloads, but check the current endpoint instructions for the request-body encoding accepted by your client. This example sends form-encoded fields.

POST with cURL

export SCREENSHOTAPI_TOKEN='YOUR_TOKEN'
curl --fail --silent --show-error \
  --request POST \
  'https://shot.screenshotapi.net/v3/screenshot' \
  --data-urlencode "token=$SCREENSHOTAPI_TOKEN" \
  --data-urlencode 'file_type=pdf' \
  --data-urlencode 'custom_html=<!doctype html><html><head><meta charset="utf-8"><style>body{font-family:Arial,sans-serif;margin:24px}h1{color:#17324d}</style></head><body><h1>Monthly report</h1><p>Generated from supplied HTML.</p></body></html>' \
  --data-urlencode 'pdf_options[format]=A4' \
  --data-urlencode 'pdf_options[print_background]=true' \
  --output report.pdf

--data-urlencode encodes HTML characters in the form value. For real documents, construct the HTML in a file or application template rather than manually editing an encoded URL. If your integration uses a different body format, confirm that format against the current API documentation.

POST with Python

import os
import requests

html = """<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <style>body { font-family: Arial, sans-serif; margin: 24px; }</style>
</head>
<body><h1>Monthly report</h1><p>Generated from supplied HTML.</p></body>
</html>"""

response = requests.post(
    "https://shot.screenshotapi.net/v3/screenshot",
    data={
        "token": os.environ["SCREENSHOTAPI_TOKEN"],
        "file_type": "pdf",
        "custom_html": html,
        "pdf_options[format]": "A4",
        "pdf_options[print_background]": "true",
    },
    timeout=120,
)
response.raise_for_status()
if "pdf" not in response.headers.get("Content-Type", "").lower():
    raise RuntimeError("The response was not identified as a PDF")
with open("report.pdf", "wb") as output:
    output.write(response.content)

POST with Node.js

import { writeFile } from "node:fs/promises";

const form = new URLSearchParams({
  token: process.env.SCREENSHOTAPI_TOKEN,
  file_type: "pdf",
  custom_html: `<!doctype html>
<html><head><meta charset="utf-8">
<style>body { font-family: Arial, sans-serif; margin: 24px; }</style>
</head><body><h1>Monthly report</h1>
<p>Generated from supplied HTML.</p></body></html>`,
  "pdf_options[format]": "A4",
  "pdf_options[print_background]": "true",
});

const response = await fetch("https://shot.screenshotapi.net/v3/screenshot", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: form,
  signal: AbortSignal.timeout(120_000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.toLowerCase().includes("pdf")) throw new Error(`Expected PDF, got ${contentType}`);
await writeFile("report.pdf", Buffer.from(await response.arrayBuffer()));

The POST examples use application/x-www-form-urlencoded. If the current service endpoint or your account’s API integration expects another body encoding, follow its current request specification. Do not silently fall back to placing a large HTML document in a GET URL.

4. Configure PDF layout

The PDF documentation lists these layout controls. Option names below are nested under pdf_options.

Option What it controls When to use it
format Standard paper size: A0 through A6, Letter, Legal, Tabloid, or Ledger. Use a standard sheet for printing, reports, invoices, or paginated documents.
width, height Custom page dimensions when a standard format is not selected. Use when your output needs a nonstandard page size. The docs state that format takes priority if both are provided.
landscape Changes the selected page orientation; the documented default is portrait. Set to true for wide tables or layouts.
page_range Retains a range such as 2-6 after the document has rendered. Use to keep selected pages from a multi-page PDF.
media Chooses the print media style when set to print. Use when the page has print-specific CSS.
print_background Requests printed backgrounds. Enable when colored backgrounds or background graphics are part of the intended design.

Examples of format values include A4 and Letter. The PDF docs say that if neither a format nor dimensions are supplied, the result is one long page without page breaks. For normal printing, choose a paper format explicitly. Set landscape=true for a wide report, and use page_range=2-6 only when the rendered output has those pages. Page ranges are applied after rendering; the docs say they have no effect on a continuous single-page PDF, and an end page beyond the rendered total is clipped to the last available page.

5. Prepare dynamic pages and inspect the result

The rendering documentation lists controls such as delay, lazy_load, and custom CSS or JavaScript injection. They can help when a page fills in content after its initial load, or needs layout adjustments. They do not guarantee every page will render correctly. Start with the simplest request, add only the controls the page needs, then inspect the PDF.

  1. Check whether the page depends on client-side rendering, delayed API calls, or lazy-loaded images.
  2. If content appears late, use the documented delay option or another documented wait control appropriate to the page.
  3. For lazy images, enable the documented lazy-loading option.
  4. Use CSS or JavaScript injection only when you need to prepare the page, for example by adjusting print styles or hiding a page element.
  5. Open the resulting PDF and check text, page breaks, fonts, images, backgrounds, and links relevant to your use case.

External fonts, images, and stylesheets must be available to the rendering environment. Raw HTML does not automatically make private URLs accessible. A protected page may require valid access setup, and a site may still refuse automated rendering.

6. Troubleshooting

Symptom Likely cause What to do
Authentication error The token is missing, invalid, revoked, or accidentally includes whitespace. Confirm the token in your dashboard, check the environment variable, and retry. Keep the token server-side.
Saved file is HTML or JSON instead of a PDF The endpoint returned an error body, or the request was not configured for PDF output. Check HTTP status and Content-Type; verify file_type=pdf and inspect the response body without overwriting a valid file.
Raw HTML is truncated or request fails with a long URL GET URL length limits were reached. Send custom_html in a POST body, as the PDF docs recommend for larger markup.
The supplied HTML seems ignored Parameter name or encoding is wrong, or the client sent an unexpected body format. Use the exact custom_html parameter, encode the form value correctly, and confirm the endpoint’s current accepted POST format. When supplied, custom_html takes precedence over url.
Images or fonts are missing Assets are blocked, inaccessible, lazy-loaded, or referenced with local file paths. Use absolute asset URLs reachable by the renderer, check access and resource loading, and enable lazy loading where appropriate.
Page is blank or content is incomplete The site may render content asynchronously, require authentication, or deny the rendering request. Check the target in a normal browser, configure required access, and try the documented delay or lazy-load options. Protected pages are not guaranteed to work.
Unexpected single long page or page range has no effect No format or dimensions were selected, so the documented default is continuous-page output. Set a standard format or both custom dimensions before using page ranges.
Wide content wraps or clips Portrait paper is too narrow for the layout. Set landscape orientation, select a wider format, or adjust print CSS.
Background colors disappear Print output may omit backgrounds unless requested, or the site uses screen-only styles. Set print_background=true and consider media=print; inspect the resulting file.
Client request times out The target is slow, the page waits on resources, or the client timeout is too short. Set a suitable client timeout, reduce unnecessary page work, and retry transient failures with bounded backoff. Avoid unlimited retries.

7. Performance, reliability, and cost

  • Request size: Raw HTML grows quickly with inline styles and scripts. POST avoids placing the payload in the URL, but does not make an arbitrarily large request safe; keep markup and assets appropriately sized.
  • Rendering time: Delays and page scripts add waiting time. Use the smallest delay that allows the needed content to appear, and avoid loading third-party resources that the PDF does not need.
  • Reliability: Check the HTTP status, response content type, and whether the saved file opens. Retry only transient network or service errors with a small bounded retry policy; a malformed request or inaccessible page needs correction rather than repeated calls.
  • Repeatability: Dynamic pages and remote assets can change between requests. For records or reports, retain the HTML/data inputs and layout parameters needed to reproduce the document.
  • Cost: Each PDF request uses the provider’s service allowance according to its current plan. Pricing can change; consult the current pricing page before estimating production cost. The research dossier does not establish a durable plan price.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API that can return a PDF as well as image formats. Use its PDF option for a page URL:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -d format=pdf \
  -o page.pdf

See the ScreenshotNeo API documentation for the available PDF parameters. Before capture, it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, and failed loads are not billed, and cache hits are not billed; responses identify the page verdict and billing status in headers. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, no card required.

FAQ

Can I use custom HTML and a URL in the same request?

You can send both, but the documentation says custom_html takes precedence and the URL is ignored when custom HTML is present.

Can I use a page range with the continuous-page default?

No. The docs say page ranges do not affect the single continuous-page output; select a page format or dimensions to create paginated output first.

Does sending raw HTML let the renderer fetch my local files?

No. Local filesystem paths belong to your machine. Use content inline or assets at URLs the rendering service can access, and configure access for restricted resources where supported.

Is a PDF guaranteed to look identical on every site?

No. Rendering depends on page styles, fonts, scripts, assets, access rules, and timing. Inspect the output for the pages and content that matter to your workflow.