Paperbolt is now live on the Shopify App Store Browse the listing

Generate PDF

Generate PDF

Generate a PDF from HTML, templates, or markdown using the /v1/pdf/generate endpoint.

Endpoint

POST https://lightningpdf.dev/api/v1/pdf/generate

Request Body

Basic HTML Generation

{
  "html": "<h1>Invoice</h1><p>Total: $100</p>"
}

Using Templates

{
  "template_id": "tpl_abc123",
  "variables": {
    "customer_name": "John Doe",
    "amount": 100,
    "items": [
      { "name": "Widget", "price": 50 },
      { "name": "Gadget", "price": 50 }
    ]
  }
}

With Options

{
  "html": "<h1>Report</h1>",
  "options": {
    "format": "A4",
    "print_background": true,
    "margin": {
      "top": "1in",
      "right": "0.5in",
      "bottom": "1in",
      "left": "0.5in"
    },
    "landscape": false,
    "display_header_footer": true,
    "header_template": "<div style='font-size: 10px; text-align: center;'>Page <span class='pageNumber'></span></div>",
    "footer_template": "<div style='font-size: 10px; text-align: center;'>© 2024 Company</div>"
  },
  "engine": "chromium"
}

Parameters

Parameter Type Required Description
html string Conditional* Raw HTML content to convert
template_id string Conditional* Template ID (starts with tpl_)
variables object No Variables to inject into template
options object No PDF generation options
engine string No Rendering engine: auto, native, chromium (default: auto)

* Either html or template_id is required

Options Object

Option Type Default Description
format string "A4" Paper format: A4, Letter, Legal, A3, A5, Tabloid
width string - Paper width (e.g., "8.5in", "210mm") overrides format
height string - Paper height (e.g., "11in", "297mm") overrides format
print_background boolean true Include background colors and images
landscape boolean false Landscape orientation
margin object {"top": "0.4in", ...} Page margins
display_header_footer boolean false Show header and footer
header_template string - HTML for header (supports <span class='pageNumber'>)
footer_template string - HTML for footer (supports <span class='pageNumber'>)
prefer_css_page_size boolean false Use CSS @page size
scale number 1.0 Scale factor (0.1 to 2.0)
delay_ms number 0 Pause (milliseconds) after the page loads before printing. Max 5000. See Rendering JavaScript content
wait_for_selector string - Wait until a DOM element matching this CSS selector exists before printing. Bounded at 10s; on timeout the PDF is produced anyway

Rendering JavaScript content

The Chromium engine prints as soon as the page's load event fires. Content that renders after load — charts drawn by JavaScript, libraries loaded from a CDN, CSS injected at runtime (e.g. the Tailwind Play CDN) — may not be in the DOM yet, and will be missing or half-drawn in the PDF.

Two ways to handle this, in order of preference:

  1. Render synchronously and inline your assets. Inline the library and the data, and produce the final markup (for charts, an inline <svg>) during initial page parse so it is present when load fires. This is the fastest and most deterministic option and needs no wait flags. Disable any entrance animations.

  2. Tell the renderer to wait. When you can't render synchronously, signal readiness:

    • wait_for_selector — after your script finishes rendering, add a sentinel element such as <div id="render-complete"></div>, and pass "wait_for_selector": "#render-complete". Preferred over a blind delay because it waits exactly as long as needed.
    • delay_ms — a fixed pause (e.g. "delay_ms": 800) when adding a sentinel isn't practical. Capped at 5000ms.
{
  "html": "<div id='chart'></div><script>/* draw chart synchronously */ document.body.insertAdjacentHTML('beforeend', '<div id=\"render-complete\"></div>');</script>",
  "options": { "wait_for_selector": "#render-complete" }
}

Both waits run inside the per-request generation timeout, so a long delay_ms plus a slow render can still time out. Keep waits as short as the content allows.

Margin Object

{
  "top": "1in",
  "right": "0.5in",
  "bottom": "1in",
  "left": "0.5in"
}

Units: px, in, cm, mm

Engine Selection

  • auto (default): LightningPDF chooses the best engine based on HTML complexity
  • native: Fast, lightweight rendering (best for simple HTML)
  • chromium: Full browser rendering (supports modern CSS, flexbox, grid, JavaScript)
{
  "html": "<div style='display: grid;'>...</div>",
  "engine": "chromium"
}

Response

{
  "success": true,
  "data": {
    "pdf": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iagoyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmoK...",
    "generation_time_ms": 187,
    "engine_used": "chromium"
  }
}

Response Fields

Field Type Description
pdf string Base64-encoded PDF file
generation_time_ms integer Generation time in milliseconds
engine_used string Actual engine used (native or chromium)

Examples

Complete Invoice Example

import requests
import base64

def generate_invoice(customer, items, total):
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        body {{ font-family: Arial, sans-serif; margin: 2cm; }}
        .header {{ text-align: center; margin-bottom: 2cm; }}
        .invoice-table {{ width: 100%; border-collapse: collapse; }}
        .invoice-table th, .invoice-table td {{
          border: 1px solid #ddd;
          padding: 8px;
          text-align: left;
        }}
        .total {{ text-align: right; font-size: 18px; margin-top: 1cm; }}
      </style>
    </head>
    <body>
      <div class="header">
        <h1>INVOICE</h1>
        <p>Customer: {customer}</p>
      </div>
      <table class="invoice-table">
        <thead>
          <tr>
            <th>Item</th>
            <th>Quantity</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>
          {''.join(f"<tr><td>{item['name']}</td><td>{item['qty']}</td><td>${item['price']}</td></tr>" for item in items)}
        </tbody>
      </table>
      <div class="total">
        <strong>Total: ${total}</strong>
      </div>
    </body>
    </html>
    """

    response = requests.post(
        "https://lightningpdf.dev/api/v1/pdf/generate",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "html": html,
            "options": {
                "format": "Letter",
                "print_background": True
            },
            "engine": "chromium"
        }
    )

    result = response.json()
    if result["success"]:
        pdf_bytes = base64.b64decode(result["data"]["pdf"])
        return pdf_bytes
    else:
        raise Exception(result["error"]["message"])

Using CSS Grid and Flexbox

curl -X POST https://lightningpdf.dev/api/v1/pdf/generate \
  -H "Authorization: Bearer lpdf_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 20px;\"><div>Left</div><div>Right</div></div>",
    "engine": "chromium"
  }'
{
  "html": "<h1>Multi-page Document</h1><p>Content...</p>",
  "options": {
    "display_header_footer": true,
    "header_template": "<div style='font-size: 9px; width: 100%; text-align: center;'><span class='title'></span></div>",
    "footer_template": "<div style='font-size: 9px; width: 100%; text-align: center;'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>",
    "margin": {
      "top": "1in",
      "bottom": "1in"
    }
  }
}

Error Codes

Code Description
invalid_html HTML content is missing or invalid
template_not_found Template ID does not exist
invalid_options Options object contains invalid values
generation_failed PDF generation failed (check HTML syntax)
insufficient_credits Not enough credits remaining

HTML Size Limits

Maximum HTML input size varies by plan:

Plan Max HTML Size
Free 5MB
Starter 10MB
Pro 25MB
Business 50MB
Enterprise 50MB

Requests exceeding the limit return a 413 status with error code PAYLOAD_TOO_LARGE.

Rate Limits

Two limits apply to every PDF request:

Daily credit quota (per plan)

Plan Credits / month
Free 100
Starter 2,500
Pro 10,000
Business 50,000
Enterprise 150,000

Exceeding the monthly quota returns 402 Payment Required with error code CREDITS_EXHAUSTED.

Per-minute PDF throughput (abuse protection)

Plan PDFs / minute
Free 5
Starter 30
Pro 60
Business 120
Enterprise 300

Exceeding the per-minute cap returns 429 Too Many Requests with these headers:

  • Retry-After: seconds until the current window resets
  • X-RateLimit-Limit: your plan's per-minute cap
  • X-RateLimit-Remaining: 0 when limited
  • X-RateLimit-Reset: Unix timestamp when the next minute window starts

Back off to Retry-After seconds before retrying. Credits are not consumed for rate-limited requests.

Error Codes

HTTP Code Meaning
400 VALIDATION_ERROR Missing required field (html, markdown, or template_id)
400 INVALID_OPTIONS PDF options outside allowed bounds (e.g. negative margin, scale outside 0.1–2.0, margin > 5in)
400 INVALID_HTML HTML could not be parsed
400 MARKDOWN_ERROR Markdown→HTML conversion failed
400 VARIABLE_VALIDATION_FAILED A template's required variable is missing from the request. details.field identifies which one.
400 TEMPLATE_PARSE_ERROR Template body contains invalid syntax (e.g. unterminated {{range}}).
400 TEMPLATE_EXECUTE_ERROR Template references a field that doesn't exist on the supplied values in a way that can't be recovered (rare).
401 UNAUTHORIZED Missing or invalid API key
402 CREDITS_EXHAUSTED Monthly credits depleted — upgrade your plan
404 TEMPLATE_NOT_FOUND template_id does not exist or isn't owned by you
413 PAYLOAD_TOO_LARGE HTML exceeds your plan's size limit
413 GENERATION_MEMORY Document too large to render — reduce pages, images, or scale
429 Rate limit exceeded See rate-limit headers above
500 GENERATION_FAILED Generic render failure — a ref: request ID is included for support
502 BROWSER_ERROR Chromium rendering engine error — retry
504 GENERATION_TIMEOUT Render took too long — simplify the document or lower scale

Every error response includes a meta.request_id you can cite when contacting support.

Response Headers

Included on every successful response:

  • X-Credits-Remaining: credits left in the current month
  • Content-Disposition: filename inferred from <title>, <h1>, or storage.file_name — override with storage.file_name in the request body