ScreenshotNeo

BlogHTML to image & PDF

Html2Pdf.app API returns a blank PDF: causes and fixes

Diagnose blank Html2Pdf.app PDFs by checking HTTP status, source access, assets, render timing, media mode, and binary response handling.

By the ScreenshotNeo team4 October 20267 min read

A blank-looking PDF from Html2Pdf.app can come from an inaccessible source or assets, content that was not ready when Chromium rendered it, print-specific CSS, or code that mishandled the response. Start by checking the HTTP status and whether the response is actually a completed PDF. Then verify the page and its resources are reachable, test the documented waitFor and media options where relevant, and save the response as bytes.

Html2Pdf.app accepts raw HTML or a publicly reachable URL in the required html field. Its documentation says conversions run in headless Chromium. A successful synchronous response contains PDF binary data; callback mode instead first returns a queue acknowledgment, followed by a callback payload with a base64-encoded document. Html2Pdf.app documentation

1. Check the status before treating the body as a PDF

Do not save every response body with a .pdf extension. Check the HTTP status first. An error response may contain diagnostic content, not a PDF. The documented errors are:

Status Documented meaning What to check
400 Inaccessible source URL or invalid parameter Verify the URL is publicly reachable and review parameter names and values.
401 Missing or invalid API key Send a valid key in the X-API-Key header.
403 Account plan limit Check the account’s plan and usage limits.
500 Unhandled server error Record the status and response details; retrying alone may not resolve the underlying issue.
202 Callback-mode job accepted The job is queued. Wait for the callback; the initial response is not the completed PDF.

For 400, 401, and 403 errors, correct the reported input, authentication, or plan issue before retrying. The service documents POST https://api.html2pdf.app/v1/generate, authenticated with an X-API-Key header. See the endpoint and error reference.

2. Run a minimal synchronous request and preserve PDF bytes

Use a public page as a controlled starting point, check the status, and write the response bytes directly. Replace the placeholder key and URL. Keep the key out of source control and logs.

cURL

curl --fail-with-body \
  -X POST "https://api.html2pdf.app/v1/generate" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html":"https://example.com"}' \
  -o output.pdf

If the command fails, inspect the HTTP status and error body instead of assuming output.pdf is valid. The --fail-with-body flag is available in current cURL versions; with an older version, check the response status separately.

Python

import requests

response = requests.post(
    "https://api.html2pdf.app/v1/generate",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"html": "https://example.com"},
    timeout=90,
)
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, got {content_type!r}")

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

Node.js

const response = await fetch("https://api.html2pdf.app/v1/generate", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ html: "https://example.com" }),
});

if (!response.ok) {
  const details = await response.text();
  throw new Error(`Html2Pdf.app returned ${response.status}: ${details}`);
}

const contentType = response.headers.get("content-type") ?? "";
if (!contentType.toLowerCase().includes("pdf")) {
  throw new Error(`Expected a PDF response, got ${contentType}`);
}

const pdfBytes = Buffer.from(await response.arrayBuffer());
const { writeFile } = await import("node:fs/promises");
await writeFile("output.pdf", pdfBytes);

These examples use a URL in html; raw markup can be supplied in that field instead. If you send raw HTML with external stylesheets, fonts, or images, those resources still need to be reachable by the rendering service.

3. Confirm the input page and assets are reachable

The vendor’s troubleshooting guidance recommends checking that the source URL is public and that required CSS, fonts, and images can be reached by the renderer. A page that works in your logged-in browser may not be available to a remote renderer.

  • Open the source URL in a private browser window with no existing login session.
  • Follow redirects and confirm the final destination is still accessible without authentication.
  • Check whether access controls, an intranet, or a firewall restrict the renderer from reaching the page.
  • For raw HTML, check every external stylesheet, font, and image URL. Prefer absolute URLs that the renderer can access.
  • Look for required assets that fail to load or are served only after a browser session is established.

Redirects and login checks are practical ways to investigate the documented public-reachability requirement; they are diagnostic checks, not separate service guarantees. The official guides specifically call out source access and asset reachability. Node.js integration and troubleshooting guide · cURL integration and troubleshooting guide

4. Check whether the page is ready when Chromium captures it

If JavaScript fills in the page or asynchronous resources arrive after the initial load, the renderer may capture before the visible content appears. Html2Pdf.app provides waitFor, a delay in seconds from 0 to 10; the default is 0. Try a small delay as a diagnostic when you have reason to expect late content:

{"html":"https://example.com","waitFor":2}

Increase the delay only as needed and compare the result. A delay can help when content arrives later; it does not fix inaccessible assets, invalid markup, authentication barriers, or every rendering issue.

5. Compare screen and print CSS

The media option selects screen or print styles and defaults to screen. A site may hide, reposition, or recolor content in print mode, while another may only define its intended document layout in print CSS. Try the mode that matches the desired output:

{"html":"https://example.com","media":"print"}

For a controlled diagnosis, compare otherwise identical requests using screen and print. If one contains the expected content, inspect the site’s corresponding media rules.

6. Handle callback mode as a separate response flow

When a request includes callBackUrl, an initial 202 Accepted means the job was queued. It does not contain the finished PDF. The callback JSON contains a base64-encoded document field. Decode that field into bytes before writing the PDF:

// In your callback handler, after parsing the JSON payload:
const pdfBytes = Buffer.from(payload.document, "base64");
await writeFile("output.pdf", pdfBytes);

In Python, the equivalent decode is base64.b64decode(payload["document"]). Treat the callback payload according to the documented format rather than writing the base64 text as if it were PDF bytes. For complete request and callback details, consult the API documentation.

7. Troubleshooting checklist

Symptom Likely cause Fix
The saved file is tiny, unreadable, or not recognized as a PDF An error or queue acknowledgment was saved as the document. Check status and content type. Handle 202 as queued work and inspect error responses.
HTTP 400 Source inaccessible or parameter invalid. Confirm public access and recheck the documented request fields and allowed option values.
HTTP 401 Missing or invalid key. Send the key as X-API-Key.
HTTP 403 Plan limit reached. Review account plan limits and usage.
PDF opens but has no page content Source or required resources may not be reachable, or the page may not be ready at capture time. Verify page and asset access; test a small waitFor only if content loads asynchronously.
Layout is unexpectedly empty or different The page’s screen and print styles differ. Compare media: "screen" with media: "print".
Callback request appears to return no PDF The initial 202 response only confirms queuing. Receive the callback and base64-decode its document field.
PDF is corrupted after download Application code converted binary data to text or JSON. Keep the response as bytes and write in binary mode or use a buffer.

8. Reliability, performance, and cost considerations

First make the source deterministic: ensure it is publicly available to the renderer and that required assets can load. Add only enough waitFor delay to accommodate known asynchronous content, since waiting longer adds latency and cannot solve access or configuration errors. For requests that may take longer in your application, set a client timeout appropriate to your workflow and distinguish transport timeouts from HTTP error responses.

For synchronous requests, save only after a successful status check. For callback mode, persist the job context and process the callback as the completion path; do not count the initial 202 as a completed document. The research sources do not publish a blank-output rate, latency benchmark, or per-request price, so none should be inferred from this troubleshooting guide. Check the service’s current plan information for costs applicable to your account.

9. Use ScreenshotNeo when the goal is a website screenshot

If you need an image capture of a web page rather than a PDF conversion, ScreenshotNeo is a website screenshot API and MCP server for developers. It returns PNG, JPEG, or WebP screenshots or a PDF from one GET request. Its clean-capture options accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the page verdict and billing state.

Here is the ScreenshotNeo one-call PDF example. See the ScreenshotNeo API documentation for request options and output configuration.

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

Use ScreenshotNeo’s MCP server with Claude, Cursor, or another MCP client when an AI agent needs to take a screenshot; its tools include take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for free: 1,000 screenshots a month, no card required.

FAQ

Does a successful synchronous request return JSON?

No. The documented synchronous success response is PDF binary data. Save the bytes after checking the HTTP status.

What does a 202 response mean?

In callback mode, it means the job was accepted into the queue. The completed PDF arrives through the callback payload.

Does increasing waitFor always fix a blank PDF?

No. It is relevant when JavaScript or asynchronous resources need extra time. It cannot make a private page or inaccessible asset reachable.

What is the default media mode?

screen. Try print when the site uses print-specific styles for its document layout.