Skip to main content
All posts
n8n automation PDF API workflow

How to Use the PodPDF API in n8n

Integrate PodPDF into n8n: generate PDFs from any trigger with the HTTP Request node, handle the response, and pass the file to the next step.

PodPDF Team April 24, 2026 7 min read

n8n is an open-source workflow automation platform that gives developers and power users deep control over their automation logic. Because n8n supports arbitrary HTTP requests and custom JavaScript, integrating PodPDF is straightforward and flexible.

This guide covers everything from basic setup to dynamic HTML templates, webhook-based async jobs, and error handling.

Prerequisites

Getting Your API Key

  1. Log into app.podpdf.com
  2. Navigate to Settings → API Keys
  3. Click Create New API Key and name it (e.g., “n8n”)
  4. Copy the key — it’s only shown once

Storing the API Key as a Credential

Store your key as an n8n credential rather than pasting it into nodes:

  1. Go to Credentials in n8n and create a new Header Auth credential
  2. Name it PodPDF API
  3. Header Name: X-API-Key
  4. Header Value: your API key

Basic Setup: HTTP Request Node

Add an HTTP Request node:

  • Method: POST
  • URL: https://api.podpdf.com/quickjob
  • Authentication: Generic Credential Type → Header AuthPodPDF API
  • Send Body: on, Body Content Type: JSON
  • Specify Body: Using JSON
{
  "input_type": "html",
  "html": "<h1>Hello from n8n</h1><p>This PDF was generated automatically.</p>",
  "options": {
    "format": "A4",
    "margin": { "top": "20mm", "bottom": "20mm", "left": "20mm", "right": "20mm" }
  }
}

Getting the PDF back

PodPDF gives you two ways to receive the PDF. Pick the one that fits the rest of your workflow.

Option A — the PDF as a file (simplest for email attachments). By default /quickjob responds with the PDF itself. In the HTTP Request node’s options, set Response Format to File and the output binary property to data. The next node — Gmail, Outlook, an S3 upload — can use that binary directly. No second download step needed.

Option B — a download link. Add "store": true to the body and PodPDF responds with JSON instead:

{
  "job_id": "9f0a4b78-2c0c-4d14-9b8b-123456789abc",
  "pages": 2,
  "truncated": false,
  "download_url": "https://...",
  "download_url_expires_at": "2026-04-24T11:00:00.000Z"
}

Later nodes can read it with {{ $json.download_url }}. The link is valid for one hour, and the file is kept for 30 days — call GET /jobs/{job_id}/download for a fresh link. Don’t save download_url anywhere as a permanent link.

Building Dynamic HTML Templates

The key to useful PDF generation is combining data from upstream nodes with an HTML template.

Build the whole request body in a Code node

Add a Code node before the HTTP Request node, and have it return the complete request body:

const item = $input.first().json;

const html = `<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding: 40px; color: #333; }
  .header { border-bottom: 2px solid #0070f3; padding-bottom: 20px; margin-bottom: 30px; }
  .company { font-size: 24px; font-weight: bold; color: #0070f3; }
  table { width: 100%; border-collapse: collapse; margin: 20px 0; }
  th { background: #f0f4ff; padding: 12px; text-align: left; border-bottom: 2px solid #0070f3; }
  td { padding: 10px 12px; border-bottom: 1px solid #eee; }
  .total { font-size: 20px; font-weight: bold; color: #0070f3; text-align: right; margin-top: 20px; }
  .footer { margin-top: 40px; font-size: 12px; color: #888; border-top: 1px solid #eee; padding-top: 20px; }
</style>
</head>
<body>
  <div class="header">
    <div class="company">Acme Corp</div>
    <h2>Invoice #${item.invoiceNumber}</h2>
    <p>Date: ${new Date(item.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
  </div>

  <p><strong>Bill To:</strong><br>
  ${item.customerName}<br>
  ${item.customerEmail}</p>

  <table>
    <tr>
      <th>Description</th>
      <th>Qty</th>
      <th>Unit Price</th>
      <th>Amount</th>
    </tr>
    ${item.lineItems.map(line => `
    <tr>
      <td>${line.description}</td>
      <td>${line.qty}</td>
      <td>$${line.unitPrice.toFixed(2)}</td>
      <td>$${(line.qty * line.unitPrice).toFixed(2)}</td>
    </tr>`).join('')}
  </table>

  <p class="total">Total: $${item.total.toFixed(2)}</p>

  <div class="footer">
    Thank you for your business. Payment due within 30 days.
  </div>
</body>
</html>`;

return {
  body: {
    input_type: 'html',
    html,
    options: { format: 'A4' },
  },
};

Then set the HTTP Request node’s JSON body to an expression:

{{ JSON.stringify($json.body) }}

Why not write "html": "{{ $json.html }}" inside a JSON template? Because real HTML is full of double quotes — class="header" above — and pasting it into a JSON string produces invalid JSON. JSON.stringify escapes everything correctly.

Complete Workflow: Invoice Generation

A full n8n workflow that generates and emails invoices from Airtable:

  1. Trigger: Airtable — Poll for new records in an “Invoices” table
  2. Code — Build the request body from the record
  3. HTTP Request (PodPDF) — Generate the PDF, with Response Format: File
  4. Gmail — Send the email, attaching the data binary property
  5. Airtable (update) — Mark the record as “Sent”

If you also want to keep the PDF, add a Google Drive or S3 upload node using the same binary, and store that file’s link on the record.

Workflow: Markdown Report to PDF

For simpler documents, use Markdown:

// Code node
const report = $input.first().json;

const markdown = `# Weekly Report — ${report.weekEnding}

## Summary

- **Revenue:** $${report.revenue.toLocaleString()}
- **New Customers:** ${report.newCustomers}
- **Churned Customers:** ${report.churned}
- **Net MRR Change:** $${report.mrrChange.toLocaleString()}

## Top Performing Channels

${report.channels.map((c, i) => `${i + 1}. ${c.name} — $${c.revenue.toLocaleString()}`).join('\n')}

## Action Items

${report.actionItems.map(item => `- [ ] ${item}`).join('\n')}

---
*Generated automatically by n8n on ${new Date().toLocaleDateString()}*`;

return {
  body: {
    input_type: 'markdown',
    markdown,
    options: {
      format: 'A4',
      margin: { top: '25mm', bottom: '25mm', left: '25mm', right: '25mm' },
    },
  },
};

Use the same {{ JSON.stringify($json.body) }} expression in the HTTP Request node.

Error Handling

A failed request returns a non-2xx status and a JSON body:

{ "error": { "code": "INSUFFICIENT_CREDITS", "message": "..." } }

In the HTTP Request node settings, set On Error to continue, and turn on the option to include the full response (status code and headers). Then branch with an IF node on the status code:

IF {{ $json.statusCode }} is 200
  → proceed to email/storage
ELSE
  → send an alert to Slack or email with {{ $json.body.error.code }}

Useful codes to handle:

CodeMeaningWhat to do
UPGRADE_REQUIRED (402)No credits purchased yetBuy credits
INSUFFICIENT_CREDITS (403)Balance ran outTop up; retrying won’t help
PAGE_LIMIT_EXCEEDED (400)Over 25 pagesUse /longjob
QUICKJOB_TIMEOUT (408)Rendering took over 30 secondsUse /longjob
429Platform throttlingRetry with a delay

Large Documents: Async Jobs and Webhooks

/quickjob is synchronous and handles up to 25 pages. For longer documents, send the same body to POST /longjob (up to 100 pages). It responds immediately with a job_id, and PodPDF calls you back when the PDF is ready:

  1. Add a Webhook node in n8n and copy its production URL
  2. In the PodPDF dashboard, register that URL as a webhook and subscribe to job.completed and job.failed
  3. The job.completed payload includes an s3_url for the PDF, valid for one hour

Many documents at once? Bulk conversion takes a whole ZIP in one job and sends bulk.job.completed, bulk.job.partial or bulk.job.failed when it’s done.

Tips for n8n + PodPDF

Use n8n credentials — Keep your API key in a Header Auth credential so it’s easy to rotate.

Build the body in Code nodesJSON.stringify handles escaping, and template literals give you full control over the HTML.

Prefer the binary response for attachments — It saves a node and avoids the one-hour link expiry entirely.

Test with manual execution — Run the workflow with test data before activating it to catch template errors early.

Merging or Splitting PDFs in Your Workflow

The same HTTP step can combine or cut up PDFs you already have. Send files to POST /pdf/merge to build one PDF from several, or to POST /pdf/split to turn a long run into one file per page or per range. See how to merge PDFs with the PodPDF API, how to split a PDF into multiple files, and the step-by-step payslip and statement splitting workflows.

Pricing

PodPDF charges $0.01 per PDF from prepaid credits that never expire (merge and split are billed once per operation), with no per-account rate limit on the Pay As You Go plan.

Create a PodPDF account and run your first n8n workflow today.

Start generating PDFs today

$0.01 per PDF — no subscriptions, no monthly minimums. Credits never expire.