This workflow corresponds to n8n.io template #18178 — we link there as the canonical source.
This workflow follows the Chainllm → 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 →
{
"id": "K1iXb57z0kdDoL0N",
"meta": {
"builderVariant": "mcp",
"aiBuilderAssisted": true
},
"name": "Reconcile two exports and diagnose why they drifted apart",
"tags": [],
"nodes": [
{
"id": "6b4e952e-82e5-4ec8-a636-00241dac16d1",
"name": "Upload Both Exports",
"type": "n8n-nodes-base.formTrigger",
"position": [
-16,
-112
],
"parameters": {
"options": {
"path": "reconcile-two-files",
"ignoreBots": true,
"buttonLabel": "Reconcile",
"appendAttribution": true
},
"formTitle": "Reconcile two exports that should agree",
"formFields": {
"values": [
{
"fieldLabel": "What are you reconciling",
"placeholder": "e.g. Payment processor payouts against our invoice ledger",
"requiredField": true
},
{
"fieldLabel": "Column that links the two files",
"placeholder": "e.g. Reference",
"requiredField": true
},
{
"fieldLabel": "Column holding the amount",
"placeholder": "e.g. Amount",
"requiredField": true
},
{
"fieldType": "file",
"fieldLabel": "File A - the source of truth",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".csv"
},
{
"fieldType": "file",
"fieldLabel": "File B - your records",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".csv"
}
]
},
"formDescription": "Upload the two files and say which column links them. You will get a workbook back showing every difference, grouped, with an explanation of why they probably drifted apart."
},
"typeVersion": 2.6
},
{
"id": "687965a0-0cd6-46ab-b599-6a1357b708ad",
"name": "Sort Out the Two Files",
"type": "n8n-nodes-base.code",
"position": [
224,
-112
],
"parameters": {
"jsCode": "const item = $input.first();\nconst bin = item.binary || {};\nconst keys = Object.keys(bin).sort();\nconst j = item.json || {};\n\nif (keys.length < 2) {\n throw new Error('Two files are required. Only ' + keys.length + ' came through - please upload both exports.');\n}\n\nreturn [{\n json: {\n subject: j['What are you reconciling'] || 'this reconciliation',\n keyColumn: String(j['Column that links the two files'] || '').trim(),\n amountColumn: String(j['Column holding the amount'] || '').trim(),\n fileAField: keys[0],\n fileBField: keys[1],\n fileAName: (bin[keys[0]] || {}).fileName || 'File A',\n fileBName: (bin[keys[1]] || {}).fileName || 'File B'\n },\n binary: item.binary\n}];"
},
"typeVersion": 2
},
{
"id": "83d0e6d5-bf81-4c5a-9387-be234c95f076",
"name": "Read the Source of Truth",
"type": "n8n-nodes-base.extractFromFile",
"position": [
496,
-112
],
"parameters": {
"options": {
"headerRow": true,
"readAsString": true,
"includeEmptyCells": true
},
"binaryPropertyName": "={{ $json.fileAField }}"
},
"typeVersion": 1.1
},
{
"id": "74068017-5913-46d8-8911-a16e4107f821",
"name": "Reconcile the Two Sides",
"type": "n8n-nodes-base.compareDatasets",
"position": [
896,
-112
],
"parameters": {
"options": {
"multipleMatches": "all"
},
"mergeByFields": {
"values": [
{
"field1": "={{ $(\"Sort Out the Two Files\").first().json.keyColumn }}",
"field2": "={{ $(\"Sort Out the Two Files\").first().json.keyColumn }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "a1b9e2d3-43c9-4e54-bbc4-76750820ba9a",
"name": "Read Your Records",
"type": "n8n-nodes-base.extractFromFile",
"position": [
496,
48
],
"parameters": {
"options": {
"headerRow": true,
"readAsString": true,
"includeEmptyCells": true
},
"binaryPropertyName": "={{ $json.fileBField }}"
},
"typeVersion": 1.1
},
{
"id": "386b49df-ad67-4665-8ffc-7fecfea9577c",
"name": "Only in the Source",
"type": "n8n-nodes-base.set",
"position": [
1120,
-128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b",
"name": "bucket",
"type": "string",
"value": "Only in the source of truth"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "dc104950-3689-4995-8dba-8afefa48bb21",
"name": "Agrees",
"type": "n8n-nodes-base.set",
"position": [
1120,
64
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b",
"name": "bucket",
"type": "string",
"value": "Agrees"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "a87020b0-b7c4-4b90-8457-8e40ecabc7df",
"name": "Values Disagree",
"type": "n8n-nodes-base.set",
"position": [
1120,
272
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b",
"name": "bucket",
"type": "string",
"value": "Values disagree"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "402ddfa8-9dc7-462b-b2f3-480591133341",
"name": "Only in Your Records",
"type": "n8n-nodes-base.set",
"position": [
1120,
480
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b",
"name": "bucket",
"type": "string",
"value": "Only in your records"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "22982683-6572-4640-b1d7-15e11bf62611",
"name": "Assemble the Reconciliation",
"type": "n8n-nodes-base.code",
"position": [
1584,
-128
],
"parameters": {
"jsCode": "const cfg = $('Sort Out the Two Files').first().json;\nconst keyCol = cfg.keyColumn;\nconst amtCol = cfg.amountColumn;\n\nconst num = function (v) {\n const n = parseFloat(String(v === undefined || v === null ? '' : v).replace(/[^0-9.-]/g, ''));\n return isNaN(n) ? 0 : n;\n};\nconst pick = function (row, col) {\n if (row[col] !== undefined) return row[col];\n const alt = Object.keys(row).filter(function (k) { return k.toLowerCase() === String(col).toLowerCase(); })[0];\n return alt ? row[alt] : '';\n};\n\nconst buckets = { 'Only in the source of truth': [], 'Agrees': [], 'Values disagree': [], 'Only in your records': [] };\nconst rows = [];\n\nfor (const it of $input.all()) {\n const r = it.json || {};\n const b = r.bucket || 'Agrees';\n if (!buckets[b]) buckets[b] = [];\n const key = String(pick(r, keyCol) || '');\n const amount = num(pick(r, amtCol));\n const entry = { key: key, amount: amount, bucket: b, raw: r };\n buckets[b].push(entry);\n rows.push(entry);\n}\n\nconst total = function (b) { return Math.round((buckets[b] || []).reduce(function (a, x) { return a + x.amount; }, 0) * 100) / 100; };\nconst counts = {\n missing: (buckets['Only in the source of truth'] || []).length,\n agrees: (buckets['Agrees'] || []).length,\n disagree: (buckets['Values disagree'] || []).length,\n extra: (buckets['Only in your records'] || []).length\n};\nconst gap = Math.round((total('Only in the source of truth') - total('Only in your records')) * 100) / 100;\nconst problems = counts.missing + counts.disagree + counts.extra;\n\nconst sampleOf = function (b, n) {\n return (buckets[b] || []).slice(0, n).map(function (x) { return x.key + ' (' + x.amount + ')'; }).join(', ');\n};\n\nreturn [{ json: {\n subject: cfg.subject,\n keyColumn: keyCol,\n amountColumn: amtCol,\n fileAName: cfg.fileAName,\n fileBName: cfg.fileBName,\n counts: counts,\n problems: problems,\n balanced: problems === 0,\n totalMissing: total('Only in the source of truth'),\n totalExtra: total('Only in your records'),\n netGap: gap,\n rowsCompared: rows.length,\n sampleMissing: sampleOf('Only in the source of truth', 12),\n sampleDisagree: sampleOf('Values disagree', 12),\n sampleExtra: sampleOf('Only in your records', 12),\n allRows: rows\n} }];"
},
"typeVersion": 2
},
{
"id": "0229c678-d9eb-4fd5-ae94-22d470ad46bb",
"name": "Diagnose Why They Drifted",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"onError": "continueRegularOutput",
"position": [
1808,
-128
],
"parameters": {
"text": "=Two exports that should agree do not. Do not list the individual differences - the workbook already does that. Explain the systemic reasons they drifted apart, so the person can fix the cause rather than tick off rows.\n\nReconciling: {{ $json.subject }}\nFile A (source of truth): {{ $json.fileAName }} | File B (your records): {{ $json.fileBName }}\nMatched on: {{ $json.keyColumn }} | Amount column: {{ $json.amountColumn }}\n\nRows compared: {{ $json.rowsCompared }}\nAgree: {{ $json.counts.agrees }}\nIn A but not B: {{ $json.counts.missing }} totalling {{ $json.totalMissing }}\nIn B but not A: {{ $json.counts.extra }} totalling {{ $json.totalExtra }}\nSame reference, different amount: {{ $json.counts.disagree }}\nNet gap: {{ $json.netGap }}\n\nExamples in A only: {{ $json.sampleMissing }}\nExamples that disagree: {{ $json.sampleDisagree }}\nExamples in B only: {{ $json.sampleExtra }}\n\nThe usual culprits in reconciliations like this are: a cut-off or timing difference between when the two exports were taken; fees, commission or tax deducted on one side only; currency conversion or rounding; refunds, credit notes or reversals netted on one side; duplicated entries; and reference format drift where the same item is written differently in each system, which shows up as an item missing from both sides at once.\n\nLook at the shape of the numbers before deciding. If the count in A only and the count in B only are similar and the amounts pair up, suspect reference format drift rather than missing records. If the disagreements are all small and in the same direction, suspect fees or rounding. Give each cause a rough share of the gap and the single quickest check that would confirm or kill it. Set materiality to clean, minor or material - material means a person should not close the period on this. Put anything the evidence does not support into do_not_conclude.",
"batching": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.9
},
{
"id": "1176105d-0e20-4252-a370-6ef48370b3c8",
"name": "Gemini for Diagnosis",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1792,
48
],
"parameters": {
"options": {
"temperature": 0.2
},
"modelName": "models/gemini-3.1-flash-lite"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "aaeae9d4-3af2-4b2f-8828-aee6fb65770b",
"name": "Diagnosis Schema",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1968,
48
],
"parameters": {
"jsonSchemaExample": "{\"headline\":\"Almost all of the gap is one cut-off week, not missing money\",\"likely_causes\":[{\"cause\":\"Payouts settled after the ledger export was taken\",\"evidence\":\"All three unmatched references are the most recent dates in file A\",\"share_of_gap\":\"about 90 percent\",\"check_first\":\"Re-export the ledger with a later cut-off and re-run\"}],\"materiality\":\"minor\",\"what_to_do_now\":[\"Re-export file B with a matching cut-off date\"],\"do_not_conclude\":[\"Do not treat the missing references as lost payments until the cut-off is ruled out\"]}"
},
"typeVersion": 1.3
},
{
"id": "3bbf120a-9921-46e3-a01a-0c3d2ec36d85",
"name": "Do They Balance",
"type": "n8n-nodes-base.if",
"position": [
2464,
-128
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $(\"Assemble the Reconciliation\").first().json.balanced }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "1591b82f-a168-42ab-9c81-1599f922c473",
"name": "Say It Balances",
"type": "n8n-nodes-base.form",
"position": [
2784,
-144
],
"parameters": {
"operation": "completion",
"respondWith": "showText",
"responseText": "=<div style=\"font-family:system-ui,sans-serif;max-width:620px;margin:64px auto;padding:0 24px;color:#1f2430;line-height:1.6\"><div style=\"font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#7a8194\">Reconciled</div><h2 style=\"margin:8px 0 6px\">{{ $(\"Assemble the Reconciliation\").first().json.subject }} balances</h2><p style=\"color:#41485c\">All {{ $(\"Assemble the Reconciliation\").first().json.counts.agrees }} rows matched on {{ $(\"Assemble the Reconciliation\").first().json.keyColumn }} and every amount agreed. Nothing appears on one side only.</p><p style=\"color:#41485c\">No workbook was generated, because there is nothing to investigate.</p><p style=\"margin-top:28px;color:#7a8194;font-size:13px\">Compared {{ $(\"Assemble the Reconciliation\").first().json.fileAName }} against {{ $(\"Assemble the Reconciliation\").first().json.fileBName }}.</p></div>"
},
"typeVersion": 2.5
},
{
"id": "a3b2a2fb-3ff1-4455-ac33-5889d6f0da20",
"name": "Build the Workbook Rows",
"type": "n8n-nodes-base.code",
"position": [
2784,
48
],
"parameters": {
"jsCode": "const a = $('Assemble the Reconciliation').first().json;\nconst d = $json.output || {};\nconst out = [];\n\nout.push({ 'Bucket': 'SUMMARY', 'Reference': a.subject, 'Amount': '', 'Detail': d.headline || '', 'Action': 'Materiality: ' + (d.materiality || 'unknown') });\nout.push({ 'Bucket': 'SUMMARY', 'Reference': 'Rows compared', 'Amount': a.rowsCompared, 'Detail': a.counts.agrees + ' agree, ' + a.counts.disagree + ' disagree, ' + a.counts.missing + ' only in source, ' + a.counts.extra + ' only in yours', 'Action': '' });\nout.push({ 'Bucket': 'SUMMARY', 'Reference': 'Net gap', 'Amount': a.netGap, 'Detail': 'Source-only total ' + a.totalMissing + ' minus yours-only total ' + a.totalExtra, 'Action': '' });\n\nfor (const c of (Array.isArray(d.likely_causes) ? d.likely_causes : [])) {\n out.push({ 'Bucket': 'LIKELY CAUSE', 'Reference': c.cause || '', 'Amount': c.share_of_gap || '', 'Detail': c.evidence || '', 'Action': c.check_first || '' });\n}\nfor (const s of (Array.isArray(d.what_to_do_now) ? d.what_to_do_now : [])) {\n out.push({ 'Bucket': 'DO NEXT', 'Reference': '', 'Amount': '', 'Detail': String(s), 'Action': '' });\n}\nfor (const s of (Array.isArray(d.do_not_conclude) ? d.do_not_conclude : [])) {\n out.push({ 'Bucket': 'DO NOT CONCLUDE', 'Reference': '', 'Amount': '', 'Detail': String(s), 'Action': '' });\n}\n\nconst order = ['Values disagree', 'Only in the source of truth', 'Only in your records', 'Agrees'];\nfor (const b of order) {\n const rows = a.allRows.filter(function (r) { return r.bucket === b; });\n if (!rows.length) continue;\n out.push({ 'Bucket': b.toUpperCase(), 'Reference': rows.length + ' row(s)', 'Amount': '', 'Detail': '', 'Action': '' });\n for (const r of rows) {\n out.push({ 'Bucket': b, 'Reference': r.key, 'Amount': r.amount, 'Detail': JSON.stringify(r.raw).slice(0, 400), 'Action': b === 'Agrees' ? '' : 'Investigate' });\n }\n}\n\nreturn out.map(function (r) { return { json: r }; });"
},
"typeVersion": 2
},
{
"id": "05f492db-eccf-482d-b4db-ec1e2c1899a5",
"name": "Make the Reconciliation Workbook",
"type": "n8n-nodes-base.convertToFile",
"position": [
3024,
48
],
"parameters": {
"options": {
"fileName": "reconciliation.xlsx",
"headerRow": true,
"sheetName": "Reconciliation"
},
"operation": "xlsx"
},
"typeVersion": 1.1
},
{
"id": "e554ad0e-00a2-4f2f-aaac-b57abcb94a3f",
"name": "Hand Back the Workbook",
"type": "n8n-nodes-base.form",
"position": [
3264,
48
],
"parameters": {
"options": {},
"operation": "completion",
"respondWith": "returnBinary",
"completionTitle": "={{ $(\"Diagnose Why They Drifted\").first().json.output.materiality === \"material\" ? \"Do not close the period yet\" : \"Reconciliation ready\" }}",
"completionMessage": "={{ $(\"Diagnose Why They Drifted\").first().json.output.headline }}\n\n{{ $(\"Assemble the Reconciliation\").first().json.problems }} row(s) need attention out of {{ $(\"Assemble the Reconciliation\").first().json.rowsCompared }} compared, a net gap of {{ $(\"Assemble the Reconciliation\").first().json.netGap }}.\n\nThe workbook opens with the likely causes ranked by how much of the gap each explains, and the single quickest check for each - then every differing row underneath. Your download should start automatically."
},
"typeVersion": 2.5
},
{
"id": "73eced58-094c-4a8c-bb50-129141184bed",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1296,
-320
],
"parameters": {
"width": 1164,
"height": 1044,
"content": "## Reconcile two exports and diagnose why they drifted apart\n\n### Why this exists\nEvery finance and ops team has a pair of systems that are supposed to agree and quietly do not. The processor against the ledger. The warehouse against the stock file. Shipped against invoiced.\n\nFinding the differing rows is the easy half, and a spreadsheet formula can do it. The hard half is the question that actually matters: is this money missing, or did the two exports just get taken at different moments? People burn afternoons on that.\n\n### How it works\nUpload the two files, name the column that links them and the column holding the amount. Everything else is inferred.\n\nn8n Compare Datasets does the matching properly, with four separate outputs rather than one merged blob: rows only in the source of truth, rows that agree, rows that match on reference but disagree on value, and rows only in your records. Each output is labelled and the four are brought back together, so nothing is lost and every row keeps its verdict.\n\nThen a Basic LLM Chain is deliberately not asked to list the individual rows - the workbook already does that. It is shown the shape of the problem: the counts in each bucket, the totals, the net gap and a sample from each side. Its job is to name the systemic cause.\n\nThat matters because the shape tells you the answer. If the count only in A and the count only in B are similar and the amounts pair up, that is reference format drift between two systems, not missing records. If every disagreement is small and in the same direction, that is fees or rounding, not fraud. If the unmatched rows are all the most recent dates, that is a cut-off difference and the money is fine. Each cause gets a rough share of the gap and the single quickest check that would confirm or kill it - plus an explicit list of what the evidence does NOT support, so nobody escalates on a hunch.\n\nIf the two files agree completely you get a page saying so and no workbook, because there is nothing to investigate. Otherwise you get an Excel file that opens with the ranked causes and what to do next, then every differing row underneath, and it downloads straight from the browser.\n\n### Setup\n1. Connect a Google Gemini (PaLM) API credential. That is the only credential needed.\n2. Open Upload Both Exports and copy the production form URL.\n3. Export both sides as CSV with a header row. They do not need the same columns - only the linking column and the amount column have to exist in both.\n\n### Customization tips\nChange the accepted file types to xlsx in the two Read nodes, add your own known causes to the diagnosis prompt, or add a node after Make the Reconciliation Workbook to file the result alongside the period close."
},
"typeVersion": 1
},
{
"id": "0dc3c1f2-1ed5-46b2-afe6-613356022ee5",
"name": "S1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-320
],
"parameters": {
"color": 7,
"width": 800,
"height": 968,
"content": "## 1. Take both files\nOne form, two uploads, and the name of the column that links them. A Code node works out which binary is which and fails loudly if only one file arrived, rather than silently reconciling a file against itself."
},
"typeVersion": 1
},
{
"id": "a8a1c351-7936-4aa8-8c4a-ccf82bbcdcc7",
"name": "S2",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
-320
],
"parameters": {
"color": 7,
"width": 688,
"height": 968,
"content": "## 2. Four answers, not one\nCompare Datasets splits the result into four separate outputs - only in the source, agrees, disagrees on value, only in your records. Each is labelled before the four are brought back together, so every row carries its own verdict into the report."
},
"typeVersion": 1
},
{
"id": "4c94d2b7-70bc-4d40-9631-da9bb8834612",
"name": "S3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1472,
-320
],
"parameters": {
"color": 7,
"width": 792,
"height": 968,
"content": "## 3. Ask why, not what\nThe chain is shown the shape of the problem - counts, totals, the net gap and a sample from each side - and never asked to list rows. The shape is what identifies the cause: paired counts mean reference drift, small one-directional gaps mean fees, recent-dates-only means a cut-off."
},
"typeVersion": 1
},
{
"id": "34dd136d-029f-46a0-9084-f7e611e848e8",
"name": "S4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2304,
-320
],
"parameters": {
"color": 7,
"width": 1184,
"height": 968,
"content": "## 4. Balanced, or a workbook that leads with the cause\nIf the two sides agree completely you get a page saying so and no file, because there is nothing to investigate. Otherwise the Excel workbook opens with the ranked causes, the quickest check for each and what the evidence does not support - then every differing row underneath."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": true,
"executionOrder": "v1"
},
"versionId": "7f875528-c783-43a0-bff7-44d0edcc3a8c",
"nodeGroups": [],
"connections": {
"Agrees": {
"main": [
[
{
"node": "Assemble the Reconciliation",
"type": "main",
"index": 0
}
]
]
},
"Do They Balance": {
"main": [
[
{
"node": "Say It Balances",
"type": "main",
"index": 0
}
],
[
{
"node": "Build the Workbook Rows",
"type": "main",
"index": 0
}
]
]
},
"Values Disagree": {
"main": [
[
{
"node": "Assemble the Reconciliation",
"type": "main",
"index": 0
}
]
]
},
"Diagnosis Schema": {
"ai_outputParser": [
[
{
"node": "Diagnose Why They Drifted",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Read Your Records": {
"main": [
[
{
"node": "Reconcile the Two Sides",
"type": "main",
"index": 1
}
]
]
},
"Only in the Source": {
"main": [
[
{
"node": "Assemble the Reconciliation",
"type": "main",
"index": 0
}
]
]
},
"Upload Both Exports": {
"main": [
[
{
"node": "Sort Out the Two Files",
"type": "main",
"index": 0
}
]
]
},
"Gemini for Diagnosis": {
"ai_languageModel": [
[
{
"node": "Diagnose Why They Drifted",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Only in Your Records": {
"main": [
[
{
"node": "Assemble the Reconciliation",
"type": "main",
"index": 0
}
]
]
},
"Sort Out the Two Files": {
"main": [
[
{
"node": "Read the Source of Truth",
"type": "main",
"index": 0
},
{
"node": "Read Your Records",
"type": "main",
"index": 0
}
]
]
},
"Build the Workbook Rows": {
"main": [
[
{
"node": "Make the Reconciliation Workbook",
"type": "main",
"index": 0
}
]
]
},
"Reconcile the Two Sides": {
"main": [
[
{
"node": "Only in the Source",
"type": "main",
"index": 0
}
],
[
{
"node": "Agrees",
"type": "main",
"index": 0
}
],
[
{
"node": "Values Disagree",
"type": "main",
"index": 0
}
],
[
{
"node": "Only in Your Records",
"type": "main",
"index": 0
}
]
]
},
"Read the Source of Truth": {
"main": [
[
{
"node": "Reconcile the Two Sides",
"type": "main",
"index": 0
}
]
]
},
"Diagnose Why They Drifted": {
"main": [
[
{
"node": "Do They Balance",
"type": "main",
"index": 0
}
]
]
},
"Assemble the Reconciliation": {
"main": [
[
{
"node": "Diagnose Why They Drifted",
"type": "main",
"index": 0
}
]
]
},
"Make the Reconciliation Workbook": {
"main": [
[
{
"node": "Hand Back the Workbook",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googlePalmApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects two CSV exports via an n8n form, reconciles them by a user-specified key and amount column, and uses Google Gemini to diagnose likely systemic causes of any drift, returning either a “balances” confirmation or a downloadable Excel reconciliation workbook.…
Source: https://n8n.io/workflows/18178/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Host Your Own AI Deep Research Agent with n8n, Apify and OpenAI. Uses outputParserStructured, lmChatOpenAi, formTrigger, chainLlm. Event-driven trigger; 87 nodes.
This template attempts to replicate OpenAI's DeepResearch feature which, at time of writing, is only available to their pro subscribers.
My workflow 53. Uses formTrigger, httpRequest, lmChatOpenAi, form. Event-driven trigger; 74 nodes.
This workflow collects candidate applications via an n8n form, extracts resume text with Mistral AI, matches the candidate to an open role stored in Google Sheets and Google Docs, scores the fit using
N8N Workflows. Uses formTrigger, agent, googleDrive, httpRequest. Event-driven trigger; 24 nodes.