Automate Invoice Entry to QuickBooks with PDF.co AI Invoice Parser

Jun 17, 2025·3 Minutes Read

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)

  1. Receive PDF file via Webhook (Download Sample File)
  2. Upload PDF to PDF.co to make it accessible for the AI Invoice Parser
  3. Split multi-page PDFs into individual invoice pages (skip this step if your file contains only one invoice)
  4. Parse each invoice page using AI to extract structured data
  5. Transform parsed data into QuickBooks API format
  6. 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
  • 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[]

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
    • Send Body: true
    • Body Content Type: JSON
    • Specify Body: Using JSON
    • Body: ={{ $json }}

Success Looks Like: A QuickBooks invoice is created for each document.

Final Test: End-to-End

  1. Prepare test PDF with multiple invoices (or use the sample PDF from this tutorial)
  2. Upload PDF to your webhook URL
  3. Execute workflow and watch the magic happen
  4. Check QuickBooks - each invoice should appear as a separate entry.

Pro Tip: Start by testing with a 2-page PDF containing 2 invoices to ensure everything works smoothly—then scale up to larger batches.

Congrats! You've automated invoice processing that scales from startup to enterprise volume.

Built something cool? Share it with us @pdfdotco

Related Tutorials

See Related Tutorials