n8n
Prerequisites
- A PolyDoc account and API key - sign up for free, no credit card required. The free plan includes 150 PDF conversions per month.
- An n8n instance - self-host for free or use n8n Cloud.
Use Case 1: Invoice PDFs from a Spreadsheet
Automatically generate an invoice PDF for every new row added to a Google Sheet and email it to the recipient.Google Sheets trigger (new row) → Google Sheets (read line items) → Code → HTTP Request (PolyDoc API) → Gmail
Prepare your data source
Create two Google Sheets tabs: invoices and invoice_items. Examples below - import via File → Import (CSV/XLSX).
| invoice_number | invoice_date | invoice_due_date | invoice_subtotal | invoice_tax_rate | invoice_tax_amount | invoice_total | customer_name | customer_email | customer_street | customer_city | customer_country |
|---|---|---|---|---|---|---|---|---|---|---|---|
| INV-2026-001 | 2026-01-15 | 2026-02-14 | 1200 | 0.1 | 120 | 1320 | John Doe | john.doe@example.com | 456 Customer Avenue | Beautiful City | UK |
| invoice_number | quantity | name | description | price | original_price |
|---|---|---|---|---|---|
| INV-2026-001 | 1 | Web Design Services | Custom website design and development for company homepage | 800 | |
| INV-2026-001 | 1 | Logo Design | Professional logo design with 3 revision rounds | 300 | |
| INV-2026-001 | 1 | SEO Optimization | Search engine optimization for 10 target keywords | 100 | 150 |
Create a workflow and add the trigger
Open your n8n instance and click Add workflow. Give it a descriptive name like "Invoices → PDF → Email".
Add Google Sheets → On Row Added as the first node. Connect your Google account, choose the spreadsheet, and select the invoices tab.
Run a test fetch so you have a sample row available for mapping in later nodes.
Next, chain nodes in this order: Google Sheets Get Row(s) on invoice_items with a built-in filter on invoice_number, Code to assemble the PolyDoc body, HTTP Request (PolyDoc), then Gmail.
Read and filter invoice_items
Add a Google Sheets node after the trigger with operation Get Row(s). Point Document at the same spreadsheet and Sheet at invoice_items.
Under Filters, click Add Filter and set:
- Column:
invoice_number - Value:
{{ $('Google Sheets Trigger').item.json.invoice_number }}(Expression mode)
Only rows matching the new invoice's number pass through - those become your line items. Using the built-in Filters section avoids a separate Filter node.
Merge header fields and build items
Add a Code node after the Filter (set mode to Run Once for All Items). Paste this script - it reads the trigger header row and all filtered line items, casts numeric fields, and emits the full PolyDoc body with source and templateData:
// Run Once for All Items mode
const TEMPLATE_ID = 'YOUR_TEMPLATE_ID'; // from the PolyDoc Dashboard
const header = $('Google Sheets Trigger').first().json;
const lineItems = $input.all();
const items = lineItems.map(item => ({
quantity: Number(item.json.quantity),
name: item.json.name,
description: item.json.description,
price: Number(item.json.price),
...(item.json.original_price
? { original_price: Number(item.json.original_price) }
: {}),
}));
return [{
json: {
source: `[template:${TEMPLATE_ID}]`,
templateData: {
invoice_number: header.invoice_number,
invoice_date: header.invoice_date,
invoice_due_date: header.invoice_due_date,
invoice_subtotal: Number(header.invoice_subtotal),
invoice_tax_rate: Number(header.invoice_tax_rate),
invoice_tax_amount: Number(header.invoice_tax_amount),
invoice_total: Number(header.invoice_total),
customer_name: header.customer_name,
customer_email: header.customer_email,
customer_street: header.customer_street,
customer_city: header.customer_city,
customer_country: header.customer_country,
items,
},
},
}];
Add the PolyDoc HTTP request
Add an HTTP Request node after the Code node and configure:
- Method: POST
- URL:
https://api.polydoc.tech/pdf/convert - Authentication: Generic Credential Type → Header Auth
- Create a credential with Name
Authorizationand ValueBearer YOUR_API_KEY(get your key from the Dashboard) - Add header:
X-Sandbox=true - Body Content Type: JSON
- Specify Body: Using JSON, set the body to
{{ $json }}- this passes the consolidated PolyDoc body (source+templateData) from the Code node straight through. - Options → Response: set Response Format to File so the PDF binary is available for the next node
Send the generated PDF
Add a Gmail node after the HTTP Request. Configure:
- To:
{{ $('Google Sheets Trigger').item.json.customer_email }} - Subject:
Your PDF - {{ $('Google Sheets Trigger').item.json.invoice_number }} - Attachments: select the binary data from the HTTP Request node
Test workflow and activate
Click Test workflow to run it once with a sample row. Verify the PDF arrives in the recipient's inbox. When satisfied, toggle the workflow to Active so it runs automatically on new rows.
Use Case 2: Webhook Returns a PDF
Accept incoming JSON via a webhook, generate a PDF with PolyDoc, and return it directly in the HTTP response.Webhook node → HTTP Request (PolyDoc API) → Respond to Webhook
Add webhook trigger
Create a new workflow. Add a Webhook node as the trigger and configure:
- HTTP Method: POST
- Respond: Using 'Respond to Webhook' Node - required so the final node can return the PDF binary instead of n8n's default JSON response.
Copy the Test URL shown in the node - you'll use it in the next step.
Send test data to the webhook
Send a test POST in the shape PolyDoc accepts - source plus a templateData object - so the next step can forward the body as-is:
curl -X POST <YOUR_TEST_URL> \
-H "Content-Type: application/json" \
-d '{
"source": "[template:YOUR_TEMPLATE_ID]",
"templateData": {
"invoice_number": "INV-2026-001",
"invoice_date": "2026-01-15",
"invoice_due_date": "2026-02-14",
"invoice_subtotal": 1200,
"invoice_tax_rate": 0.1,
"invoice_tax_amount": 120,
"invoice_total": 1320,
"customer_name": "John Doe",
"customer_email": "john.doe@example.com",
"customer_street": "456 Customer Avenue",
"customer_city": "Beautiful City",
"customer_country": "UK",
"items": [
{"quantity": 1, "name": "Web Design Services", "description": "Custom website design and development for company homepage", "price": 800},
{"quantity": 1, "name": "Logo Design", "description": "Professional logo design with 3 revision rounds", "price": 300},
{"quantity": 1, "name": "SEO Optimization", "description": "Search engine optimization for 10 target keywords", "price": 100, "original_price": 150}
]
}
}'
n8n captures the payload and exposes it as {{ $json.body }} in the Webhook node output. If your caller can't shape the body this way, insert a Code node before the HTTP Request (see Use Case 1 Step 4 for the pattern) to wrap arbitrary inputs into source + templateData.
Add PolyDoc HTTP request
Add an HTTP Request node and configure it the same way as in Use Case 1:
- URL:
https://api.polydoc.tech/pdf/convert - Method: POST
- Header Auth credential:
Authorization=Bearer YOUR_API_KEY - Header:
X-Sandbox=true - Body Content Type: JSON
- Specify Body: Using JSON, set the body to
{{ $json.body }}- the webhook payload already carries the canonicalsource+templateDatashape, so it forwards untouched. - Response Format: File
Respond with the PDF binary
Add a Respond to Webhook node. Set Respond With to Binary and select the PDF data from the previous node. This returns the generated PDF directly to the caller.
Test workflow and activate
Click Execute workflow on the canvas and POST another test payload to confirm the PDF is returned. When satisfied, click Publish in the top-right - the Production URL (under the Webhook node's Production URL tab, different from the Test URL) then accepts requests continuously, no Execute-workflow click required per call.