ScreenshotNeo

BlogHTML to image & PDF

How to Generate a PDF from HTML Content Instead of a URL with Html2Pdf.app

Send your HTML directly to Html2Pdf.app as JSON, then save the successful response as PDF bytes. This guide covers cURL, Python, Node.js, layout options, errors, and async callbacks.

By the ScreenshotNeo team4 October 20268 min read

If your application already has the HTML, send it in the html field of an authenticated JSON POST request to https://api.html2pdf.app/v1/generate. On success, the response body is the PDF itself as binary data: save or stream those bytes. You do not need to publish the HTML at a URL first. The same html field also accepts a public URL when you want to convert an existing page. Html2Pdf.app API documentation.

This guide shows the inline HTML workflow in cURL, Python, and Node.js, explains layout and callback options, and covers common rendering and HTTP errors.

1. Send raw HTML in an authenticated POST request

  1. Build or render the HTML string in your application.
  2. POST JSON to the generate endpoint with Content-Type: application/json.
  3. Send your API key in the X-API-Key header.
  4. Check the HTTP status. For a successful response, write the body bytes to a PDF file or stream them to the caller.

Use a JSON serializer in application code. It safely encodes quotes, newlines, backslashes, and other characters that would otherwise break the JSON request.

cURL

curl --fail --show-error \
  --request POST https://api.html2pdf.app/v1/generate \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --data '{
    "html": "<h1>Invoice INV-1042</h1><p>Total: $240.00</p>",
    "format": "A4",
    "marginTop": 40,
    "marginRight": 32,
    "marginBottom": 40,
    "marginLeft": 32
  }' \
  --output invoice.pdf

--fail makes cURL treat an HTTP error status as a failed command, and --show-error displays its error message. Without status handling, an error response could be saved under a .pdf filename. The vendor’s cURL guide demonstrates sending inline markup this way.

Python

import os
import requests

html = """<!doctype html>
<html>
  <head><meta charset="utf-8"><title>Invoice</title></head>
  <body><h1>Invoice INV-1042</h1><p>Total: $240.00</p></body>
</html>"""

response = requests.post(
    "https://api.html2pdf.app/v1/generate",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": os.environ["HTML2PDF_API_KEY"],
    },
    json={
        "html": html,
        "format": "A4",
        "marginTop": 40,
        "marginRight": 32,
        "marginBottom": 40,
        "marginLeft": 32,
    },
    timeout=90,
)
response.raise_for_status()

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

The json= argument serializes the object, and wb writes the returned PDF bytes without text encoding changes. Keep the API key in a server-side environment variable.

Node.js

const fs = require('node:fs/promises');

async function main() {
  const html = `<!doctype html>
<html>
  <head><meta charset="utf-8"><title>Invoice</title></head>
  <body><h1>Invoice INV-1042</h1><p>Total: $240.00</p></body>
</html>`;

  const response = await fetch('https://api.html2pdf.app/v1/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.HTML2PDF_API_KEY,
    },
    body: JSON.stringify({
      html,
      format: 'A4',
      marginTop: 40,
      marginRight: 32,
      marginBottom: 40,
      marginLeft: 32,
    }),
  });

  if (!response.ok) {
    const details = await response.text();
    throw new Error(`PDF generation failed (${response.status}): ${details}`);
  }

  const pdf = Buffer.from(await response.arrayBuffer());
  await fs.writeFile('invoice.pdf', pdf);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

response.arrayBuffer() reads the binary response; do not use response.json() or convert the successful body to a string. The example uses Node.js’s built-in fetch and requires a Node version that provides it.

2. Choose input, delivery, and page layout

Raw HTML or a public URL

Use raw markup when your application has generated the document, such as an invoice, report, or receipt. Put the markup string in html. Use a public URL when the source is an existing page the rendering service can reach. For raw content, JSON POST avoids the URL encoding and length problems of putting HTML into a GET query string. Html2Pdf.app documents both input forms in the API reference.

Layout and media settings

The documented layout controls include page format or dimensions, orientation, margins, and screen or print media mode. The example sets A4 and margins in the request body. Consult the current parameter reference for exact names, accepted values, units, and limits before relying on a particular configuration.

  • Page size: Select a format or dimensions suitable for the document.
  • Orientation: Choose portrait or landscape to fit the content width.
  • Margins: Set page margins for readable output and avoid clipping near page edges.
  • Media mode: Choose screen or print styling to control which CSS rules apply.

CSS media mode, available fonts and other resources, and JavaScript load timing can change the rendered result. If you use remote stylesheets, images, or fonts, verify that the rendering service can reach them. For application-generated documents, self-contained HTML and reachable dependencies make rendering more predictable.

Synchronous response or callback

A synchronous request returns the PDF bytes in the successful HTTP response. This is the simplest flow when the caller can wait for generation to finish.

For background generation, include callBackUrl. The documented flow responds with 202 Accepted to indicate the request was queued, then POSTs to the callback URL with the PDF encoded as base64 in document. Callback delivery may be retried, so make the callback handler idempotent: identify the job or document, and avoid creating duplicate records or processing the same PDF twice. The callback URL must be reachable by the service. Check the current documentation for the exact callback payload and supported request options.

3. Handle errors and rendering problems

Symptom or status Likely cause Fix
400 Invalid parameter or inaccessible source URL. Correct the request fields and values. If using a URL, verify that it is publicly reachable by the renderer.
401 Missing or invalid API key. Send the valid key in X-API-Key. Check server environment configuration and avoid exposing the key in client-side code.
403 Current plan limit reached or request not allowed by the plan. Review plan limits and usage, then adjust the plan or request as appropriate.
500 Unhandled service error. Record the status and error details, then retry with a sensible limit and backoff if the operation is safe to repeat.
Blank PDF HTML, scripts, or referenced resources were not ready or accessible when rendered. Check that the HTML contains the expected content, resources can be fetched, and JavaScript has time to populate the page.
Missing styles, images, or fonts Referenced assets cannot be reached, or the selected media mode applies different CSS. Verify resource URLs and permissions, confirm the selected screen or print mode, and check font availability.
PDF file contains an error message The client saved a non-success response body as if it were a PDF. Check the HTTP status before writing the body; inspect error responses as text rather than saving them as a document.
Malformed request with quotes or newlines HTML was manually inserted into JSON without escaping. Use a JSON serializer or the HTTP client’s JSON option instead of assembling JSON by string concatenation.

The status mappings and rendering caveats above come from the vendor’s API documentation and cURL guide. Do not repeatedly retry invalid requests such as 400, 401, or 403 without correcting their cause.

4. Production considerations

Security

  • Keep the API key in backend code, an environment variable, or a trusted job runner. Never put it in browser JavaScript, public repositories, or client-side templates.
  • Do not accept arbitrary HTML from untrusted users and forward it without considering your application’s input and data handling requirements.
  • Use HTTPS and avoid logging the API key or sensitive document contents.

Reliability and performance

  • Set a client timeout appropriate to your document and workflow. Handle timeouts separately from HTTP errors because the client may not know whether a remote job completed.
  • For synchronous generation, return or stream bytes only after confirming success. Avoid loading large PDFs into memory unnecessarily when your server framework supports streaming.
  • Use the callback flow for jobs that should run in the background. Make callback processing idempotent because delivery can be retried.
  • For transient server or network failures, use bounded retries with backoff and an idempotency strategy in your own application. Do not blindly retry every status.
  • Rendering time depends on the document, resource availability, and JavaScript timing. The research sources provide no independently verified performance benchmark or guarantee, so size timeouts based on your own workload.

Cost and limits

The cited documentation identifies plan limits as a possible reason for 403, but the available research does not establish current prices, quotas, or a cost per PDF. Check the vendor’s current plan and API information before estimating production costs. Prevent accidental spend by validating input size and applying request limits in your own application.

Or skip the browser setup

If your goal is to capture a page that already has a URL, ScreenshotNeo is a website screenshot API and MCP server: one GET request returns a PNG, JPEG, WebP, or PDF. Its API captures a URL; this does not replace the raw-HTML-to-PDF workflow above when your markup has no URL.

cURL:

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

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options. Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use the screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for free and get 1,000 screenshots a month with no card.

FAQ

What should I put in the html field?

Put the HTML markup string there, or a public page URL if you want the service to fetch an existing page. For generated content, send the markup itself.

Does the successful API response contain JSON?

No. The synchronous success response is binary PDF content. Read it as bytes and save or stream it.

Can I convert a document without making it public?

Raw HTML can be submitted directly in the authenticated POST body, so you do not have to publish it at a URL first. Keep the API key and document handling on the server side.

When should I use a callback?

Use the callback option when you want generation handled in the background and can process a later POST containing the base64 PDF. For a basic request where the caller can wait, use the synchronous response.