Pro Tip: Start by testing with a 2-page PDF containing 2 invoices to ensure everything works smoothly—then scale up to larger batches.
Automate Invoice Entry to QuickBooks with PDF.co AI Invoice Parser
What You'll Have When Done: Upload PDFs containing one or many invoices, and have them automatically parsed and created as individual invoices in QuickBooks Online.
Prerequisites
Make sure you have these set up:
- PDF.co API Key
- n8n instance (self-hosted or n8n.cloud)
- QuickBooks Online account (sandbox or production)
- QuickBooks Developer App with OAuth2 credentials added in n8n
Quick Start Options
Option A: I Want It Working Now
- Import this workflow template → Download JSON File and Download Sample File
- Connect your PDF.co and QuickBooks accounts
- Test with a sample invoice PDF
- Customize customer/item mappings as needed
Option B: I Want to Build It Follow the 7-step guide below to create the automation from scratch.
What This Automation Does (Overview)
- Receive PDF file via Webhook (Download Sample File)
- Upload PDF to PDF.co to make it accessible for the AI Invoice Parser
- Split multi-page PDFs into individual invoice pages (skip this step if your file contains only one invoice)
- Parse each invoice page using AI to extract structured data
- Transform parsed data into QuickBooks API format
- Create individual QuickBooks invoices for each parsed invoice
Step 1: Set Up Webhook to Receive PDFs
- Node: Webhook
- Settings:
- Field Name for Binary Data:
data
- Field Name for Binary Data:
- Test: Click "Listen for Test Event", copy the webhook URL, and send a sample file to it.
Success Looks Like: A file is successfully received, showing data0
as its binary name.
Step 2: Upload PDF to PDF.co
- Node: PDF.co API → Upload File
- Settings:
- Action: Upload File
- Binary File:
true
- Input Binary Field:
data0
- File Name:
file.pdf
Success Looks Like:
{
"url": "https://pdf-co-temp-files.s3..../file.pdf..."
}
Step 3: Split Multi-Page PDF
- Node: PDF.co API → Split PDF
- Settings:
- Action: Split PDF
- URL:
={{ $json.url }}
(from upload step) - Pages:
*
(split individual pages)
Success Looks Like:
[
{
"body": [
"https://pdf-co-temp-files.s3.../page1-1.pdf...",
"https://pdf-co-temp-files.s3.../page2-2.pdf..."
]
}
]
Step 4: Split Array into Individual Items
- Node: Split Out
- Settings:
- Field to Split Out:
body
- Include: Selected Other Fields
- Fields to Include:
body[]
- Field to Split Out:
Success Looks Like: Each PDF page URL becomes a separate workflow item for individual processing.
[
{
"body": "https://pdf-co-temp-files.s3.../page1-1.pdf..."
},
{
"body": "https://pdf-co-temp-files.s3.../page2-2.pdf..."
}
]
Step 5: AI Parse Each Invoice
- Node: PDF.co API → AI Invoice Parser
- Settings:
- Action: AI Invoice Parser
- URL:
={{ $json.body }}
(from split out step)
Success Looks Like:
[
{
"body": {
"invoice": {
"invoiceNo": "INV-78904123",
"invoiceDate": "2025-06-11"
},
"lineItems": [
{
"description": "Website Design",
"quantity": "3",
"total_usd": "$1,485.00"
}
],
"paymentDetails": {
"subtotal": "$3,185.00",
"tax": "$254.80",
"total": "$3,439.80"
}
}
}
]
Step 6: Transform Data for QuickBooks
- Node: Code
- Language: JavaScript
- Code:
// Process ALL items from the parser, not just the first one
const allItems = $input.all();
console.log(`Processing ${allItems.length} invoices from parser`);
const allInvoices = [];
// Loop through each parsed invoice
for (let i = 0; i < allItems.length; i++) {
const inputItem = allItems[i];
console.log(`\n=== Processing Invoice ${i + 1} ===`);
// Get the invoice data - try different possible structures
let invoiceData;
if (inputItem.json?.body) {
invoiceData = inputItem.json.body;
} else if (inputItem.json) {
invoiceData = inputItem.json;
} else {
invoiceData = inputItem;
}
console.log(`Invoice ${i + 1} data:`, JSON.stringify(invoiceData, null, 2));
// Check if this item has lineItems
if (!invoiceData.lineItems || !Array.isArray(invoiceData.lineItems)) {
console.log(`Invoice ${i + 1}: No valid lineItems found, skipping`);
continue;
}
console.log(`Invoice ${i + 1}: Found ${invoiceData.lineItems.length} line items`);
// Transform line items for this invoice
const qbLineItems = invoiceData.lineItems.map((item, index) => ({
Id: (index + 1).toString(),
LineNum: index + 1,
Description: item.description || `Item ${index + 1}`,
Amount: parseFloat((item.total_usd || item.total || "0").toString().replace(/[$,]/g, '')),
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: {
value: "1", // Replace Item ID
name: "Services"
},
Qty: parseFloat((item.quantity || "1").toString()),
ItemAccountRef: {
value: "1",
name: "Services"
},
TaxCodeRef: {
value: "NON"
}
}
}));
// Add subtotal line
qbLineItems.push({
Amount: parseFloat((invoiceData.paymentDetails?.subtotal || "0").toString().replace(/[$,]/g, '')),
DetailType: "SubTotalLineDetail",
SubTotalLineDetail: {}
});
// Create QuickBooks invoice for this parsed invoice
const invoice = {
CustomerRef: {
value: "1", // Replace Customer ID
name: "Test Customer"
},
DocNumber: invoiceData.invoice?.invoiceNo || `INV-${Date.now()}-${i + 1}`,
TxnDate: new Date().toISOString().split('T')[0],
CurrencyRef: {
value: "USD",
name: "United States Dollar"
},
Line: qbLineItems,
BillAddr: {
Line1: invoiceData.customer?.billTo?.address?.streetAddress || "123 Main St",
City: invoiceData.customer?.billTo?.address?.city || "Default City",
PostalCode: invoiceData.customer?.billTo?.address?.postalCode || "12345"
},
TotalAmt: parseFloat((invoiceData.paymentDetails?.total || "0").toString().replace(/[$,]/g, '')),
TxnTaxDetail: {
TotalTax: parseFloat((invoiceData.paymentDetails?.tax || "0").toString().replace(/[$,]/g, ''))
}
};
console.log(`Invoice ${i + 1} QB format:`, JSON.stringify(invoice, null, 2));
allInvoices.push({ json: invoice });
}
console.log(`\n=== SUMMARY ===`);
console.log(`Parsed ${allItems.length} items from parser`);
console.log(`Created ${allInvoices.length} QuickBooks invoices`);
// Return ALL processed invoices
return allInvoices;
Success Looks Like: Each parsed invoice transformed into QuickBooks API format.
Step 7: Create QuickBooks Invoices
- Node: HTTP Request
- Settings:
- Method:
POST
- URL:
https://sandbox-quickbooks.api.intuit.com/v3/company/YOUR_COMPANY_ID/invoice
(if using Sandbox) - Authentication: Predefined Credential Type
- Credential Type: QuickBooks OAuth2 API
- Headers:
- Accept:
application/json
- Content-Type:
application/json
- Accept:
- Send Body:
true
- Body Content Type:
JSON
- Specify Body:
Using JSON
- Body:
={{ $json }}
- Method:
Success Looks Like: A QuickBooks invoice is created for each document.
Final Test: End-to-End
- Prepare test PDF with multiple invoices (or use the sample PDF from this tutorial)
- Upload PDF to your webhook URL
- Execute workflow and watch the magic happen
- Check QuickBooks - each invoice should appear as a separate entry.
Congrats! You've automated invoice processing that scales from startup to enterprise volume.
Built something cool? Share it with us @pdfdotco
Related Tutorials



