Tutorials

Python Generate PDF: Production API Guide for Developers

Learn how to generate PDF files with Python, APIs, Chromium rendering, failure fixes, costs, and production deployment choices.

M Mikel Rougstone · 26 September 2026 · 7 min read
Python Generate PDF: Production API Guide for Developers

TL;DR

  • Python generate PDF workflows can use a hosted API instead of managing browser containers. PDFGeny sends HTML, a URL or one of 40 ready document templates to POST https://pdfgeny.com/api/v1/render and returns a PDF.
  • PDFGeny uses headless Chromium as the default renderer, with WeasyPrint and Ghostscript PDF/A-2b output available for specific document requirements.
  • A cold headless Chromium process can cost about 7.8 seconds per request, while a warm browser can run at about 0.65 seconds. Browser lifecycle management is often the hidden production problem.
  • PDFGeny reports a median render time of 0.6 seconds, supports batches of up to 100 documents in one call, and offers a free plan with 50 documents a month without requiring a card.
  • Get a free API key if your application needs HTML, URL or template input with a finished PDF returned through one request.

How to generate PDF files with Python in production

Python generate PDF tasks are usually solved with a library, a browser automation stack or a hosted rendering API. A production API approach sends document input to POST https://pdfgeny.com/api/v1/render and lets the rendering service handle Chromium, fonts and PDF creation. PDFGeny reports a median render time of 0.6 seconds for its rendering workflow.

The basic Python integration is small because the difficult part is not the HTTP request. The difficult part is keeping rendering consistent across invoices, receipts, contracts and reports when browsers, fonts and operating systems change.

import requests

payload = {
    "html": "<html><body><h1>Invoice #1042</h1><p>Amount: $250</p></body></html>"
}

response = requests.post(
    "https://pdfgeny.com/api/v1/render",
    json=payload,
    headers={
        "Authorization": "Bearer YOUR_API_KEY"
    }
)

response.raise_for_status()

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

Failure mode: page breaks. HTML that looks correct in a browser window can produce incorrect printed pages because PDF rendering follows print rules. Tables split across pages, headings separate from content and fixed-height containers create clipped text.

The fix is not adding random margins until the output looks right. Use print CSS deliberately.

<style>
@media print {.invoice-row {
    break-inside: avoid;
  }

  h2 {
    break-after: avoid;
  }
}
</style>

External references such as the CSS Fragmentation specification from the W3C describe the rules behind page-breaking behavior: CSS Fragmentation Module Level 3.

Why hosted PDF generation replaces fragile browser stacks

The hidden cost of running Chromium yourself

Headless Chromium is a common choice because modern web layouts already exist as HTML and CSS. The production issue is not rendering a single file. The issue is operating the browser process reliably.

Failure mode: cold browser startup. A cold headless Chromium process costs about 7.8 seconds per request. Keeping the browser warm reduces that startup cost to about 0.65 seconds. This difference explains why server-side PDF systems often need browser pools, process supervision and memory limits.

A local Puppeteer or Playwright deployment can still be the right choice if the team already operates containers and needs complete browser control. The trade-off is operational ownership: Chromium updates, font packages, sandbox settings and crash recovery become application responsibilities.

PDFGeny exposes sync and async jobs, signed HMAC webhooks and stored documents so applications can choose between immediate generation and background processing.

Python PDF generation options: library, browser or API

The correct choice depends on document volume, layout complexity and infrastructure ownership. A small internal script and a customer-facing billing system have different requirements.

ApproachBest fitMain trade-off
Python PDF librariesSmall document sets and fixed layoutsLess HTML/CSS compatibility
Playwright or Puppeteer with ChromiumTeams needing browser-level controlBrowser operations become part of the product
Hosted PDF APIApplications generating invoices, reports and certificatesRequires network access and API management

When a local library is better

The non-obvious answer is that an API is not always the best engineering choice. For a handful of documents a day, a local library often beats an API because deployment simplicity matters more than distributed rendering.

A Python library such as ReportLab can be a better fit for a small utility that creates a few predictable PDFs and does not need browser-style HTML rendering. Adding an external service for five static reports a day can create unnecessary authentication and network handling.

The decision changes when the application needs customer-facing documents, many templates, async processing or consistent rendering across environments.

PDFGeny feature comparison

FeatureValue
API endpointPOST https://pdfgeny.com/api/v1/render
Input methodsHTML, URL or templates
Template library40 ready document templates
Batch generationUp to 100 documents in one call
Free usage50 documents a month, no card required
Overage$0.009 per document

Developers comparing approaches can also review the production considerations in PDF generator API real costs and performance in production and Chrome HTML document to PDF production issues.

Real production failures: fonts, SSRF and framework traps

Fonts are a rendering dependency

Failure mode: web fonts silently falling back. A PDF renderer can finish before remote fonts load, producing a PDF that uses fallback fonts without an obvious error.

The fix is to package fonts locally when possible, wait for document readiness and verify generated files visually. A PDF with the wrong font can change line wrapping, which then changes page breaks.

URL rendering creates an SSRF risk

Failure mode: URL-to-PDF endpoints become SSRF holes. A service that fetches arbitrary URLs can accidentally request private network resources, loopback addresses or cloud metadata endpoints.

The fix is URL validation before fetching. A production renderer should resolve the hostname, reject private and loopback IP ranges, block metadata addresses and control outbound network access.

OWASP documents SSRF prevention patterns in its Server-Side Request Forgery guidance.

Django async rendering issue

Failure mode: sync_playwright inside Django raises SynchronousOnlyOperation. This happens when synchronous browser work runs inside an async context.

The fix is moving the Playwright rendering operation onto its own thread or using an architecture designed for asynchronous jobs.

from threading import Thread
from playwright.sync_api import sync_playwright

def create_pdf():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.set_content("<h1>Report</h1>")
        page.pdf(path="report.pdf")
        browser.close()

thread = Thread(target=create_pdf)
thread.start()
thread.join()

What We Got Wrong / What Surprised Us

The strongest non-obvious finding is that the browser is often not the difficult part. The difficult part is everything around the browser: fonts, network rules, memory limits and lifecycle management.

A PDF generator is not only a file converter. It is a rendering environment with security, dependency and operational concerns.

A common assumption is that replacing wkhtmltopdf automatically solves PDF problems. That misses the actual issue. wkhtmltopdf is not dead; it is unmaintained. The bigger cost is that older rendering engines do not support the modern CSS features developers expect.

Another mistake is treating PDF output as a simple download response. Production systems need document status handling, retries and delivery workflows. PDFGeny supports async jobs and HMAC-signed webhooks because generation is often part of a larger application process.

Practical Takeaways

  • Choose the rendering model first. Spend 30 minutes identifying whether your documents need HTML/CSS, fixed layouts or archival PDF/A output. Difficulty: Easy. Expected outcome: fewer tool changes later.
  • Test page breaks with real documents. Spend 1-2 hours creating long invoices, tables and multi-page reports. Difficulty: Medium. Expected outcome: fewer production formatting bugs.
  • Handle fonts before deployment. Spend 1 hour checking local and web fonts. Difficulty: Medium. Expected outcome: consistent typography across generated files.
  • Secure URL rendering. Spend 2-4 hours adding hostname resolution checks and private network blocking. Difficulty: Hard. Expected outcome: reduced SSRF exposure.
  • Pick API versus library based on volume and ownership. Spend 30 minutes comparing maintenance costs. Difficulty: Easy. Expected outcome: a deployment model that matches the application.

Developers moving document workflows from scripts can also review HTML file to PDF production API patterns and generate PDF from text API approaches.

Need production PDF generation without maintaining Chromium containers, browser updates and font installation? PDFGeny accepts HTML, URLs and templates, then returns finished PDFs through an API call. The free plan includes 50 documents a month.

Get a free API key

FAQ: Python generate PDF questions

What is the fastest way to generate a PDF with Python?

The fastest implementation depends on the document type. A Python HTTP request to a PDF API can create a document without installing a renderer. PDFGeny reports a median render time of 0.6 seconds, while local browser setups must manage browser startup and resources.

Is ReportLab better than an HTML-to-PDF API?

ReportLab is often better for a small number of fixed-layout documents. An HTML-to-PDF API is usually a better fit when applications need web layouts, templates, async jobs or centralized rendering.

Can Python generate PDF from HTML?

Yes. Python can send HTML to a rendering API or control a local browser engine. The engineering challenge is not creating bytes; it is maintaining consistent output with fonts, CSS and security controls.

What causes PDFs generated from HTML to look different from browsers?

PDF rendering follows print rules rather than interactive browser behavior. Common causes include missing fonts, unsupported CSS, page-break rules and resources that load too late.

:::

M

Mikel Rougstone

Founder, PDFGeny

I build and run PDFGeny — the API, the rendering fleet and the template catalog. Most of what I write here comes from something that broke in production first.