AutomationFlowsAI & RAG › Post Bank Statement Transactions to Quickbooks Online Using Openrouter AI

Post Bank Statement Transactions to Quickbooks Online Using Openrouter AI

ByInfyOm Technologies @infyom on n8n.io

Manually entering bank statements into QuickBooks is one of the most time-consuming and error-prone accounting tasks. Accountants often spend hours converting PDF bank statements into individual income and expense entries—risking missed transactions, incorrect categorization,…

Event trigger★★★★☆ complexityAI-powered29 nodesAgentOutput Parser StructuredHTTP RequestForm TriggerOpenRouter ChatQuickBooks
AI & RAG Trigger: Event Nodes: 29 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #12344 — we link there as the canonical source.

This workflow follows the Agent → Form Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "a6f5bd1b-9488-4414-afca-e9ed592e52b6",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -3264,
        2456
      ],
      "parameters": {
        "options": {
          "password": "YOUR_CREDENTIAL_HERE"
        },
        "operation": "pdf",
        "binaryPropertyName": "Bank_Statement_PDF"
      },
      "typeVersion": 1.1
    },
    {
      "id": "340e4c28-389a-4efc-af96-86c601cccdf0",
      "name": "Transaction Extractor AI",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueRegularOutput",
      "position": [
        -3040,
        2456
      ],
      "parameters": {
        "text": "=Extract all financial transactions from the provided bank statement text.\n\n{{ $json.text }}\n\nFor EACH transaction, do the following carefully:\n\n1. Determine whether the transaction is:\n   - \"credit\" \u2192 money received / deposited\n   - \"debit\" \u2192 money spent / withdrawn\n\n2. Extract:\n   - Transaction date (convert to YYYY-MM-DD format)\n   - Full transaction description\n   - Transaction amount (always a positive number)\n   - Reference number if present (otherwise return an empty string)\n   - Payee or counterparty name (clean and human-readable)\n\nImportant rules:\n- Do NOT return explanations or extra text.\n- Do NOT wrap the result in an object.\n- Return ONLY a JSON array.\n- Each transaction must strictly follow the defined schema.\n- If any field is missing in the statement, return an empty string (not null).\n\nReturn format example:\n[\n  {\n    \"transactionType\": \"debit\",\n    \"date\": \"YYYY-MM-DD\",\n    \"description\": \"string\",\n    \"amount\": number,\n    \"referenceNumber\": \"string\",\n    \"payee\": \"string\"\n  }\n]",
        "options": {
          "systemMessage": "You are a financial data extraction expert. You specialize in reading bank statements and converting them into structured transaction data. You must be precise with dates, amounts, and credit/debit classification. Output must strictly match the requested JSON schema with no additional text."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "6444a755-5ad6-4019-b0e5-683745b66876",
      "name": "JSON Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -2864,
        2688
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"array\",\n  \"items\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"transactionType\": {\n        \"type\": \"string\",\n        \"enum\": [\"credit\", \"debit\"]\n      },\n      \"date\": {\n        \"type\": \"string\",\n        \"pattern\": \"^\\\\d{4}-\\\\d{2}-\\\\d{2}$\"\n      },\n      \"description\": {\n        \"type\": \"string\"\n      },\n      \"amount\": {\n        \"type\": \"number\"\n      },\n      \"referenceNumber\": {\n        \"type\": \"string\"\n      },\n      \"payee\": {\n        \"type\": \"string\"\n      }\n    },\n    \"required\": [\n      \"transactionType\",\n      \"date\",\n      \"description\",\n      \"amount\",\n      \"referenceNumber\",\n      \"payee\"\n    ]\n  }\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "b0ab778f-6d40-4311-8f4d-421c13662097",
      "name": "Create QuickBooks SalesReceipt",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -208,
        2368
      ],
      "parameters": {
        "url": "https://sandbox-quickbooks.api.intuit.com/v3/company/company_id/salesreceipt?minorversion=75",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "quickBooksOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "ae994181-acf4-427a-93e3-f93a15901e9b",
      "name": "Bank Statement Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -3488,
        2456
      ],
      "parameters": {
        "options": {
          "appendAttribution": false
        },
        "formTitle": "QuickBooks Bank Statement Upload",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "Bank Statement PDF",
              "requiredField": true,
              "acceptFileTypes": ".pdf"
            }
          ]
        },
        "formDescription": "Upload your bank statement PDF to automatically create entries in QuickBooks"
      },
      "typeVersion": 2.3
    },
    {
      "id": "52ea829a-a625-4793-ab63-b40c41520f84",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -3040,
        2688
      ],
      "parameters": {
        "model": "openai/gpt-oss-20b:free",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "af43d4d4-9ca9-44d3-96d6-0a1c63eb7e67",
      "name": "Split Transactions",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2688,
        2456
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "output"
      },
      "typeVersion": 1
    },
    {
      "id": "dee42064-962d-48bb-a33d-09a2fc48c455",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -2464,
        2456
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "b41ef097-b891-4704-8684-470f43d44ee9",
      "name": "Build Salesreceipt Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        -416,
        2368
      ],
      "parameters": {
        "jsCode": "// // --------------------------------\n// // 1. Get transaction correctly\n// // --------------------------------\n// const txn = $('Loop Over Items').first().json;\n\n// // --------------------------------\n// // 2. Get resolved itemRef\n// // --------------------------------\n// const itemRef = $('Collect All Item Mappings').first().json.itemRef;\n\n// // --------------------------------\n// // 3. Hard validation\n// // --------------------------------\n// if (!txn.amount) {\n//   throw new Error('Transaction amount missing');\n// }\n\n// if (!itemRef?.value) {\n//   throw new Error('ItemRef not resolved');\n// }\n\n// const amount = Number(txn.amount);\n\n// if (isNaN(amount)) {\n//   throw new Error('Amount is not a number');\n// }\n\n// // --------------------------------\n// // 4. Build SalesReceipt payload\n// // --------------------------------\n// const payload = {\n//   Line: [\n//     {\n//       LineNum: 1,\n//       Amount: amount,\n//       Description: txn.description || '',\n//       DetailType: 'SalesItemLineDetail',\n//       SalesItemLineDetail: {\n//         ItemRef: {\n//           value: itemRef.value,\n//           name: itemRef.name\n//         },\n//         Qty: 1,\n//         UnitPrice: amount,\n//         TaxCodeRef: {\n//           value: 'NON'\n//         }\n//       }\n//     }\n//   ],\n//   TxnDate: txn.date,\n//   PrivateNote: `${txn.description} - Ref: ${txn.referenceNumber || 'N/A'}`,\n//   CustomerRef: {\n//     value: '1'\n//   },\n//   DepositToAccountRef: {\n//     value: '35'\n//   },\n//   TotalAmt: amount\n// };\n\n// return { json: payload };\n\n\n\n// --------------------------------\n// 1. Transaction\n// --------------------------------\nconst txn = $('Loop Over Items').first().json;\n\n// --------------------------------\n// 2. ItemRef\n// --------------------------------\nconst itemRef = $('Collect All Item Mappings').first().json.itemRef;\n\n// --------------------------------\n// 3. Resolve CustomerRef DIRECTLY\n// --------------------------------\nlet customer;\n\n// Try existing customer first\ntry {\n  customer = $('Get many customers').first().json;\n} catch (_) {}\n\n// If not found, use newly created customer\nif (!customer?.Id) {\n  customer = $('Create a Customer').first().json;\n}\n\nif (!customer?.Id) {\n  throw new Error('Customer not resolved');\n}\n\nconst customerRef = {\n  value: customer.Id,\n  name: customer.DisplayName\n};\n\n// --------------------------------\n// 4. Validation\n// --------------------------------\nconst amount = Number(txn.amount);\n\nif (!amount || isNaN(amount)) {\n  throw new Error('Invalid transaction amount');\n}\nif (!itemRef?.value) {\n  throw new Error('ItemRef missing');\n}\n\n// --------------------------------\n// 5. SalesReceipt Payload\n// --------------------------------\nconst payload = {\n  Line: [\n    {\n      LineNum: 1,\n      Amount: amount,\n      Description: txn.description || '',\n      DetailType: 'SalesItemLineDetail',\n      SalesItemLineDetail: {\n        ItemRef: {\n          value: itemRef.value,\n          name: itemRef.name\n        },\n        Qty: 1,\n        UnitPrice: amount,\n        TaxCodeRef: { value: 'NON' }\n      }\n    }\n  ],\n  TxnDate: txn.date,\n  PrivateNote: `${txn.description} - Ref: ${txn.referenceNumber || 'N/A'}`,\n  CustomerRef: customerRef,\n  DepositToAccountRef: { value: '35' },\n  TotalAmt: amount\n};\n\nreturn { json: payload };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "5733b401-f55c-4062-8717-35befaeddee3",
      "name": "Get many customers",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -976,
        2336
      ],
      "parameters": {
        "filters": {
          "query": "=WHERE DisplayName = '{{ $json.itemRef.name }}'"
        },
        "operation": "getAll"
      },
      "typeVersion": 1
    },
    {
      "id": "cf1debb7-ccf5-4d31-96e8-593ac5592260",
      "name": "Create Vendor",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -1280,
        2672
      ],
      "parameters": {
        "resource": "vendor",
        "operation": "create",
        "displayName": "={{ $('Loop Over Items').item.json.payee }}",
        "additionalFields": {
          "CompanyName": "={{ $('Loop Over Items').item.json.payee }}"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2b8e13cf-c84d-4d03-950f-1a2819cb435a",
      "name": "Vendor Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1504,
        2752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "string",
                "operation": "empty"
              },
              "leftValue": "={{ $json.Id }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "47ab4b59-e851-4956-987a-1607cee94918",
      "name": "Find Vendor",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -1728,
        2752
      ],
      "parameters": {
        "filters": {
          "query": "=WHERE DisplayName = '{{ $('Loop Over Items').item.json.payee }}'"
        },
        "resource": "vendor",
        "operation": "getAll"
      },
      "typeVersion": 1
    },
    {
      "id": "9411680b-8cd9-4c75-b8c4-9011caff2531",
      "name": "Search Categories",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1952,
        2752
      ],
      "parameters": {
        "url": "https://sandbox-quickbooks.api.intuit.com/v3/company/company_id/query?minorversion=75",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "SELECT Id, Name, AccountType, AccountSubType FROM Account"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "quickBooksOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "789c680c-6b68-4d79-8656-4c145c188a08",
      "name": "Create Items",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1360,
        2272
      ],
      "parameters": {
        "url": "https://sandbox-quickbooks.api.intuit.com/v3/company/company_id/item?minorversion=75",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"Name\": \"{{ $json.itemsToCreate[0].name }}\",\n  \"Description\": \"{{ $json.itemsToCreate[0].description }}\",\n  \"Type\": \"{{ $json.itemsToCreate[0].type }}\",\n  \"IncomeAccountRef\": {\n    \"value\": \"{{ $json.itemsToCreate[0].incomeAccountRef }}\"\n  },\n  \"ExpenseAccountRef\": {\n    \"value\": \"{{ $json.itemsToCreate[0].expenseAccountRef }}\"\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "quickBooksOAuth2Api"
      },
      "typeVersion": 4.3
    },
    {
      "id": "eeb77754-2940-483b-8d6d-31247eff0196",
      "name": "Collect All Item Mappings",
      "type": "n8n-nodes-base.code",
      "position": [
        -1168,
        2336
      ],
      "parameters": {
        "jsCode": "// // --------------------------------\n// // 1. From previous decision node\n// // --------------------------------\n// const decision = $('Check Which Items to Create').first().json;\n\n// // --------------------------------\n// // 2. If item was created\n// // --------------------------------\n// if (decision.needsCreation) {\n//   const created = $('Create Items').first().json;\n//   const item = created.Item || created;\n\n//   return [{\n//     json: {\n//       itemRef: {\n//         value: item.Id,\n//         name: item.Name\n//       }\n//     }\n//   }];\n// }\n\n// // --------------------------------\n// // 3. If existing item was found\n// // --------------------------------\n// const key = decision.desiredItemName;\n// const existing = decision.existingItemMapping[key];\n\n// return [{\n//   json: {\n//     itemRef: {\n//       value: existing.id,\n//       name: existing.name\n//     }\n//   }\n// }];\n\n\n\n// --------------------------------\n// 1. Decision\n// --------------------------------\nconst decision = $('Check Which Items to Create').first().json;\n\n// --------------------------------\n// 2. If item was newly created\n// --------------------------------\nif (decision.needsCreation) {\n  const created = $('Create Items').first().json;\n  const item = created.Item || created;\n\n  return [{\n    json: {\n      itemRef: {\n        value: item.Id,\n        name: item.Name\n      }\n    }\n  }];\n}\n\n// --------------------------------\n// 3. If existing item was found\n// --------------------------------\nconst key = decision.desiredItemName;\nconst existing = decision.existingItemMapping[key];\n\n// \ud83d\udd10 SAFETY CHECK\nif (!existing) {\n  throw new Error(`Item mapping missing for: ${key}`);\n}\n\nreturn [{\n  json: {\n    itemRef: {\n      value: existing.id,\n      name: existing.name\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2b87de0a-cca5-46b4-82ee-a001b356af29",
      "name": "Need to Create Items?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1584,
        2320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.needsCreation }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "fdce277b-d161-4117-88e6-36cdabd6ae09",
      "name": "Get All QB Items",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -1968,
        2320
      ],
      "parameters": {
        "filters": {},
        "resource": "item",
        "operation": "getAll",
        "returnAll": true
      },
      "typeVersion": 1
    },
    {
      "id": "96c9f261-ee74-4a10-8214-59ec302eaa72",
      "name": "Check Which Items to Create",
      "type": "n8n-nodes-base.code",
      "position": [
        -1776,
        2320
      ],
      "parameters": {
        "jsCode": "// // --------------------------------\n// // 1. Inputs\n// // --------------------------------\n// const txn = $('Loop Over Items').item.json;\n// const qbItems = $input.all(); // from \"Get All QB Items\"\n\n// // --------------------------------\n// // 2. Normalize name logic\n// // --------------------------------\n// const normalize = (s) =>\n//   s.toLowerCase().replace(/[^a-z0-9]/g, ' ').trim();\n\n// // Item name derived from transaction\n// const desiredItemName = normalize(txn.payee || txn.description);\n\n// // --------------------------------\n// // 3. Build existing item lookup\n// // --------------------------------\n// const existingMap = {};\n\n// qbItems.forEach(n => {\n//   if (n.json?.Name && n.json?.Id) {\n//     existingMap[normalize(n.json.Name)] = {\n//       id: n.json.Id,\n//       name: n.json.Name\n//     };\n//   }\n// });\n\n// // --------------------------------\n// // 4. Decide create or reuse\n// // --------------------------------\n// const itemsToCreate = [];\n// const existingItemMapping = {};\n\n// if (existingMap[desiredItemName]) {\n//   existingItemMapping[desiredItemName] = existingMap[desiredItemName];\n// } else {\n//   itemsToCreate.push({\n//     name: txn.payee || 'Service',\n//     description: txn.description,\n//     type: 'Service',\n//     incomeAccountRef: '79',   // Income account\n//     expenseAccountRef: '80'   // Expense account\n//   });\n// }\n\n// return [{\n//   json: {\n//     desiredItemName,\n//     itemsToCreate,\n//     existingItemMapping,\n//     needsCreation: itemsToCreate.length > 0\n//   }\n// }];\n\n\n\n// --------------------------------\n// 1. Inputs\n// --------------------------------\nconst txn = $('Loop Over Items').first().json;\nconst qbItems = $input.all();\n\n// --------------------------------\n// 2. Utility\n// --------------------------------\nconst normalize = s =>\n  s.toLowerCase().replace(/[^a-z0-9]/g, ' ').trim();\n\nconst shortenName = (name) => {\n  if (!name) return 'Service';\n  return name\n    .replace(/services?/gi, '')\n    .replace(/limited|ltd|pvt|private/gi, '')\n    .trim();\n};\n\n// --------------------------------\n// 3. Desired item name\n// --------------------------------\nconst desiredItemName = normalize(shortenName(txn.payee || txn.description));\n\n// --------------------------------\n// 4. Existing item map\n// --------------------------------\nconst existingMap = {};\n\nqbItems.forEach(n => {\n  if (n.json?.Name && n.json?.Id) {\n    existingMap[normalize(n.json.Name)] = {\n      id: n.json.Id,\n      name: n.json.Name\n    };\n  }\n});\n\n// --------------------------------\n// 5. Decide create or reuse\n// --------------------------------\nconst itemsToCreate = [];\nconst existingItemMapping = {};\n\nif (existingMap[desiredItemName]) {\n  existingItemMapping[desiredItemName] = existingMap[desiredItemName];\n} else {\n  itemsToCreate.push({\n    name: shortenName(txn.payee),\n    description: txn.description,\n    type: 'Service',\n    incomeAccountRef: '79',\n    expenseAccountRef: '80'\n  });\n}\n\nreturn [{\n  json: {\n    desiredItemName,\n    itemsToCreate,\n    existingItemMapping,\n    needsCreation: itemsToCreate.length > 0\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fdc9ab25-31f3-4d09-b478-79f87fa05914",
      "name": "Credit or Debit?",
      "type": "n8n-nodes-base.switch",
      "position": [
        -2192,
        2464
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Credit",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "655116ac-b3b9-42d8-965e-7a6dc73d096b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.transactionType }}",
                    "rightValue": "credit"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Debit",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "179cb4e7-89b9-4b98-9f95-109d6efedf20",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.transactionType }}",
                    "rightValue": "debit"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "0bc3819d-3c03-4fd7-a411-c55f21564300",
      "name": "Customers Exists?1",
      "type": "n8n-nodes-base.if",
      "position": [
        -784,
        2336
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "string",
                "operation": "empty"
              },
              "leftValue": "={{ $json.Id }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "12636d7f-5d9a-4882-8216-f63566c6dc99",
      "name": "Create a Customer",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -576,
        2272
      ],
      "parameters": {
        "operation": "create",
        "displayName": "={{ $('Collect All Item Mappings').item.json.itemRef.name }}",
        "additionalFields": {
          "CompanyName": "={{ $('Collect All Item Mappings').item.json.itemRef.name }}"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d87eca6-2b68-44c7-a81b-a7b6595fb2b4",
      "name": "Create QuickBooks Expense",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -832,
        2752
      ],
      "parameters": {
        "url": "https://sandbox-quickbooks.api.intuit.com/v3/company/company_id/purchase?minorversion=75",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"TxnDate\": \"{{ $json.TxnDate }}\",\n  \"PrivateNote\": \"{{ $json.PrivateNote }}\",\n\n  \"EntityRef\": {\n    \"type\": \"Vendor\",\n    \"value\": \"{{ $json.EntityRef.value }}\",\n    \"name\": \"{{ $json.EntityRef.name }}\"\n  },\n\n  \"PaymentType\": \"{{ $json.PaymentType }}\",\n\n  \"AccountRef\": {\n    \"value\": \"35\",\n    \"name\": \"Checking\"\n  },\n\n  \"Line\": [\n    {\n      \"Amount\": {{ $json.Line[0].Amount }},\n      \"DetailType\": \"AccountBasedExpenseLineDetail\",\n      \"AccountBasedExpenseLineDetail\": {\n        \"AccountRef\": {\n          \"value\": \"{{ $json.Line[0].AccountBasedExpenseLineDetail.AccountRef.value }}\",\n          \"name\": \"{{ $json.Line[0].AccountBasedExpenseLineDetail.AccountRef.name }}\"\n        },\n        \"TaxCodeRef\": {\n          \"value\": \"NON\"\n        }\n      }\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "quickBooksOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "bcaacfb1-d417-4372-b1e5-0870a00c8897",
      "name": "Build Expense Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        -1056,
        2752
      ],
      "parameters": {
        "jsCode": "// // -----------------------------\n// // 1. Transaction\n// // -----------------------------\n// const txn = $('Loop Over Items').item.json;\n\n// // -----------------------------\n// // 2. Vendor (from Merge node)\n// // -----------------------------\n// const vendor = $input.first().json;\n\n// if (!vendor || !vendor.Id) {\n//   throw new Error(`Vendor not resolved for payee: ${txn.payee}`);\n// }\n\n// // -----------------------------\n// // 3. Build Expense Payload\n// // -----------------------------\n// const expensePayload = {\n//   TxnDate: txn.date,\n//   PrivateNote: `${txn.description} - Ref: ${txn.referenceNumber || 'N/A'}`,\n//   EntityRef: {\n//     type: \"Vendor\",\n//     value: vendor.Id,\n//     name: vendor.DisplayName || vendor.CompanyName\n//   },\n//   PaymentType: \"Cash\",\n//   TotalAmt: txn.amount,\n//   Line: [\n//     {\n//       Amount: txn.amount,\n//       DetailType: \"AccountBasedExpenseLineDetail\",\n//       AccountBasedExpenseLineDetail: {\n//         AccountRef: {\n//           value: \"35\",   // \u2705 Bank Account (Checking)\n//           name: \"Checking\"\n//         },\n//         TaxCodeRef: {\n//           value: \"NON\"\n//         }\n//       }\n//     }\n//   ]\n// };\n\n// return { json: expensePayload };\n\n\n\n\n// -----------------------------\n// 1. Transaction\n// -----------------------------\nconst txn = $('Loop Over Items').item.json;\nconst description = (txn.description || '').toLowerCase();\n\n// -----------------------------\n// 2. Vendor (from Merge logic)\n// -----------------------------\nconst vendor = $input.first().json;\n\nif (!vendor || !vendor.Id) {\n  throw new Error(`Vendor not resolved for payee: ${txn.payee}`);\n}\n\n// -----------------------------\n// 3. Load Categories (Chart of Accounts)\n// -----------------------------\nconst categoryItems =\n  $items('Search Categories')[0]?.json?.QueryResponse?.Account || [];\n\nif (!categoryItems.length) {\n  throw new Error('No categories found from QuickBooks');\n}\n\n// Build lookup map: name -> account\nconst categoryMap = {};\nfor (const acc of categoryItems) {\n  if (acc.AccountType === 'Expense') {\n    categoryMap[acc.Name.toLowerCase()] = acc;\n  }\n}\n\n// -----------------------------\n// 4. Auto Category Mapping Logic\n// -----------------------------\nlet selectedCategory = null;\n\nif (description.includes('upi') || description.includes('bank') || description.includes('charge')) {\n  selectedCategory = categoryMap['bank charges'];\n}\nelse if (description.includes('insurance')) {\n  selectedCategory = categoryMap['insurance expense'];\n}\nelse if (description.includes('ads') || description.includes('advertising')) {\n  selectedCategory = categoryMap['advertising'];\n}\nelse if (description.includes('vendor') || description.includes('service')) {\n  selectedCategory = categoryMap['professional fees'];\n}\n\n// Default fallback\nif (!selectedCategory) {\n  selectedCategory = categoryMap['services'];\n}\n\nif (!selectedCategory) {\n  throw new Error('No matching expense category found');\n}\n\n// -----------------------------\n// 5. Build Expense Payload (CORRECT)\n// -----------------------------\nconst expensePayload = {\n  TxnDate: txn.date,\n  PrivateNote: `${txn.description} - Ref: ${txn.referenceNumber || 'N/A'}`,\n  EntityRef: {\n    type: 'Vendor',\n    value: vendor.Id,\n    name: vendor.DisplayName || vendor.CompanyName\n  },\n  PaymentType: 'Cash',\n  TotalAmt: txn.amount,\n  Line: [\n    {\n      Amount: txn.amount,\n      DetailType: 'AccountBasedExpenseLineDetail',\n      AccountBasedExpenseLineDetail: {\n        AccountRef: {\n          value: selectedCategory.Id,\n          name: selectedCategory.Name\n        },\n        BillableStatus: 'NotBillable',\n        TaxCodeRef: {\n          value: 'NON'\n        }\n      }\n    }\n  ]\n};\n\nreturn { json: expensePayload };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7c1aedbd-4bc1-4d3c-84e0-2c84f273b474",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3984,
        2288
      ],
      "parameters": {
        "width": 380,
        "height": 508,
        "content": "## How it works\nUpload a password-protected or normal bank statement PDF through a web form. AI extracts all transactions and classifies them as credits (income) or debits (expenses). Credits create Sales Receipts with auto-generated customers and items. Debits create Purchase records with vendors and expense categories. The workflow handles duplicate checking and creates missing QuickBooks entities automatically.\n\n## Setup steps\n1. Connect your QuickBooks Online account (sandbox or production)\n2. Add OpenRouter API credentials for AI extraction\n3. Update the PDF password in Extract PDF Text node\n4. Replace 'company_id' in HTTP Request URLs with your QuickBooks Company ID\n5. Verify account IDs (35 for Checking, 79/80 for Income/Expense)\n6. Test with a sample bank statement"
      },
      "typeVersion": 1
    },
    {
      "id": "683f4fc6-6b8a-4b6e-bc24-21be977797b2",
      "name": "PDF Extraction",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3296,
        2256
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 588,
        "content": "## PDF Extraction\nForm trigger receives bank statement PDF. Text is extracted and sent to AI agent with structured output parser to identify all transactions with dates, amounts, descriptions, and payees."
      },
      "typeVersion": 1
    },
    {
      "id": "97fdacea-2f20-461f-8ddc-b4d2703bace2",
      "name": "Transaction Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2720,
        2256
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 588,
        "content": "## Transaction Processing\nSplit extracted transactions into individual items. Loop processes each one, routing credits and debits to separate paths based on transaction type."
      },
      "typeVersion": 1
    },
    {
      "id": "c77e9110-11ff-4932-acc4-50e1f12a362a",
      "name": "Credit Path",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2016,
        2144
      ],
      "parameters": {
        "color": 7,
        "width": 1752,
        "height": 420,
        "content": "## Credit Path (Income)\nFetches existing QuickBooks items or creates new service items. Checks for existing customers by name, creates if missing. Builds and posts Sales Receipt to record income transaction."
      },
      "typeVersion": 1
    },
    {
      "id": "ba30de8a-1dbf-474b-aef5-927c4bb45459",
      "name": "Debit Path",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2016,
        2576
      ],
      "parameters": {
        "color": 7,
        "width": 1376,
        "height": 420,
        "content": "## Debit Path (Expenses)\nLoads expense categories from Chart of Accounts. Searches for vendor by payee name, creates if not found. Auto-maps transaction to expense category based on description keywords. Posts Purchase record to QuickBooks."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Find Vendor": {
      "main": [
        [
          {
            "node": "Vendor Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Items": {
      "main": [
        [
          {
            "node": "Collect All Item Mappings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Vendor": {
      "main": [
        [
          {
            "node": "Build Expense Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Vendor Exists?": {
      "main": [
        [
          {
            "node": "Create Vendor",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Expense Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Credit or Debit?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Credit or Debit?": {
      "main": [
        [
          {
            "node": "Get All QB Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Search Categories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Transaction Extractor AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All QB Items": {
      "main": [
        [
          {
            "node": "Check Which Items to Create",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a Customer": {
      "main": [
        [
          {
            "node": "Build Salesreceipt Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Categories": {
      "main": [
        [
          {
            "node": "Find Vendor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Customers Exists?1": {
      "main": [
        [
          {
            "node": "Create a Customer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Salesreceipt Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many customers": {
      "main": [
        [
          {
            "node": "Customers Exists?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JSON Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Transaction Extractor AI",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Split Transactions": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bank Statement Form": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Expense Payload": {
      "main": [
        [
          {
            "node": "Create QuickBooks Expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Need to Create Items?": {
      "main": [
        [
          {
            "node": "Create Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Collect All Item Mappings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Transaction Extractor AI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Transaction Extractor AI": {
      "main": [
        [
          {
            "node": "Split Transactions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect All Item Mappings": {
      "main": [
        [
          {
            "node": "Get many customers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create QuickBooks Expense": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Salesreceipt Payload": {
      "main": [
        [
          {
            "node": "Create QuickBooks SalesReceipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Which Items to Create": {
      "main": [
        [
          {
            "node": "Need to Create Items?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create QuickBooks SalesReceipt": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Manually entering bank statements into QuickBooks is one of the most time-consuming and error-prone accounting tasks. Accountants often spend hours converting PDF bank statements into individual income and expense entries—risking missed transactions, incorrect categorization,…

Source: https://n8n.io/workflows/12344/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

The AI-Powered Shopify SEO Content Automation is an enterprise-grade workflow that transforms product content creation for e-commerce stores. This sophisticated multi-agent system integrates GPT-4o, C

Perplexity Tool, Memory Buffer Window, Agent +15
AI & RAG

Deep Research new (fr). Uses outputParserStructured, formTrigger, chainLlm, form. Event-driven trigger; 82 nodes.

Output Parser Structured, Form Trigger, Chain Llm +8
AI & RAG

🧠 Automate end-to-end SEO blog creation and WordPress publishing using a GPT-5 multi-agent workflow with real-time research, metadata generation, and optional featured images.

Output Parser Structured, HTTP Request, OpenAI +10
AI & RAG

YouTube Strategist. Uses formTrigger, splitOut, splitInBatches, agent. Event-driven trigger; 50 nodes.

Form Trigger, Agent, OpenRouter Chat +5
AI & RAG

This workflow is designed for e-commerce, marketing teams, or creators who want to automate the production of high-quality, AI-generated product visuals and ad creatives.

HTTP Request, Error Trigger, Discord +7