DocPress

PDF generation in Python

Rendering PDFs inside a Python service usually means installing system libraries or driving a headless browser. With an HTTP API the dependency is requests: post Markdown or HTML, write the bytes to a file or stream them to the user.

Minimal example

import os, requests

r = requests.post(
    "https://docpress.dev/v1/pdf",
    headers={"Authorization": f"Bearer {os.environ['DOCPRESS_KEY']}"},
    json={"markdown": "# Weekly report\n\n## Summary\n\nAll systems nominal.", "theme": "report", "cover": True, "toc": True},
    timeout=60,
)
r.raise_for_status()
open("weekly-report.pdf", "wb").write(r.content)
print(r.headers["X-Pages"], "pages;", r.headers["X-Quota-Used"], "of", r.headers["X-Quota-Limit"], "used this month")

An official Python SDK is coming; until then the call above is the whole integration.

HTML from a template engine

Render your template to a string, then post it with theme: "none" and your CSS. Inline images as data: URIs.

import base64, pathlib, requests

logo = base64.b64encode(pathlib.Path("logo.png").read_bytes()).decode()
html = render_template("invoice.html", invoice=inv, logo=f"data:image/png;base64,{logo}")
css = pathlib.Path("print.css").read_text()
r = requests.post("https://docpress.dev/v1/pdf", headers=HEADERS,
                  json={"html": html, "css": css, "theme": "none", "page_size": "Letter", "filename": f"{inv.number}.pdf"}, timeout=60)

Handling errors

Errors come back as JSON with a detail message. Treat them differently:

  • 422: the document could not be rendered; the message says why (typically an external URL). Fix the input; do not retry unchanged.
  • 429: monthly quota reached. Queue the job and alert someone; retrying will not help until the plan changes or the month rolls over.
  • 413: source over 2 MB. Shrink embedded images.
  • 401: wrong or missing key.
if r.status_code == 422:
    raise ValueError(r.json()["detail"])

Streaming to a web response

In a web framework, pass r.content straight through with Content-Type: application/pdf and a Content-Disposition header. Keep the DocPress key on the server; never call the API from the browser.

Limits and pricing

  • No JavaScript is executed. Send the final HTML; anything a script would have drawn or filled in will not appear.
  • The renderer has no network access. Images, fonts and stylesheets at external URLs are never fetched; inline them as data: URIs. A request that references an external URL fails with a 422 that names the URL, rather than silently dropping the asset.
  • 2 MB of source per request, 300 pages per document, 2 concurrent renders per account.
  • Free-plan output carries a small docpress.dev footer mark. Paid plans (from $9/month) do not.

Plans are capped. When a plan's monthly document count is reached the API returns 429 until you upgrade or the month rolls over; there is no overage billing.

  • Free: 50 documents/month, footer mark on output, no card required.
  • Starter: $9/month, 500 documents, no mark.
  • Pro: $29/month, 3,000 documents.
  • Scale: $79/month, 15,000 documents.

Every response includes X-Quota-Used and X-Quota-Limit headers so you can watch usage without polling. Details on the pricing page.

Questions

Is there a Python package?

An official SDK is coming. The API is a single JSON endpoint, so requests (or httpx, or urllib) is sufficient today.

Can I run this in a serverless function?

Yes. There is nothing to install beyond an HTTP client, and no binary dependencies, which is the usual pain point for PDF generation in serverless runtimes.

How do I include images?

Base64-encode the file and reference it as a data: URI in the HTML or Markdown. External URLs are never fetched.

Is the PDF stored on your side?

No. The response is the only copy. Save it where you need it.

Related

Free previews carry a watermark and are deleted after 24 hours. Free API output carries a footer mark. The renderer runs no JavaScript and never fetches external URLs. Privacy · Terms · Refunds