AutomationFlowsAI & RAG › Reconcile Cash Balances in Google Sheets with Openai Explanations

Reconcile Cash Balances in Google Sheets with Openai Explanations

ByWeblineIndia @weblineindia on n8n.io

This workflow automatically compares internal cash balances with custodian or bank balances using Google Sheets, detects mismatches by , calculates balance differences, logs matched records and sends mismatched records through OpenAI for a short explanation before saving them…

Cron / scheduled trigger★★★★☆ complexityAI-powered17 nodesGoogle SheetsOpenAI
AI & RAG Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Google Sheets → OpenAI 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
{
  "id": "FChFYOPW7ReutJkS",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Cash Reconciliation Checker",
  "tags": [],
  "nodes": [
    {
      "id": "667627cb-1d7c-4681-8364-c1efb7f768e5",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        544
      ],
      "parameters": {
        "width": 704,
        "height": 624,
        "content": "## Cash Reconciliation Workflow\n\nAutomates balance reconciliation by comparing internal and custodian cash balances, identifying mismatches and routing records for logging, AI-based analysis and Slack alerts.\n\n## Flow:\n\nSchedule Trigger \u2192 Fetch Sheets \u2192 Edit Fields \u2192 Match Accounts \u2192 Calculate Difference \u2192 Mismatch Check\n\n## Setup Steps\n\nCreate two Google Sheets for internal and custodian balances using the same account_id values. Ensure balance columns contain numeric data only. Configure Schedule Trigger timing, map fields correctly in Google Sheets nodes and connect Slack for mismatch alerts. Verify AI node, logging sheet and output columns are properly configured.\n\n## How It Works\n\nThe workflow runs on schedule, fetches both balance sheets, standardizes fields and matches records by account_id. It calculates the balance difference and checks for mismatches. Matching records are logged directly. Mismatched records go through AI explanation, are prepared for reporting, logged into Sheets and sent as Slack alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "589ada80-e623-41a0-a14d-480e9758e1db",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1664,
        1456
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 528,
        "content": "## Scheduled Balance Fetch\n\nTriggers the workflow on a schedule and pulls both internal finance balances and custodian/bank balances from Google Sheets for reconciliation."
      },
      "typeVersion": 1
    },
    {
      "id": "f0af182d-1077-4a37-b5f5-b2fece0bcd61",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2992,
        1456
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 528,
        "content": "## Reconciliation Outcome Check\n\nEvaluates the calculated balance difference and routes each record based on whether it is **matched** or **mismatched** for further logging and exception handling.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1d9ea856-87b8-491a-a394-a77ba032f117",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3392,
        1456
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 528,
        "content": "## AI Exception Analysis\n\nTakes mismatched reconciliation records, sends them to the AI model for a short possible explanation and prepares the result for exception logging and alerting.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "91596983-4ce7-4ba0-828f-70e308a316c6",
      "name": "Run Reconciliation on Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1680,
        1728
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "86dba853-c610-498a-9c6e-eb60a83d24f4",
      "name": "Fetch Internal Balances",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1888,
        1632
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1aybRV31sefahegSLnKXYlg0f9NyId4e-6_M3KUzRRTk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1aybRV31sefahegSLnKXYlg0f9NyId4e-6_M3KUzRRTk/edit?usp=drivesdk",
          "cachedResultName": "Internal Balance Sheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "13d797e3-7816-4bf3-b9c1-8621722be228",
      "name": "Fetch Custodian Balances",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1888,
        1824
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1m2VP3OdOJr4HgzWO_VjgeC9H-EXTjm8AtrbgYEu5xyI/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1m2VP3OdOJr4HgzWO_VjgeC9H-EXTjm8AtrbgYEu5xyI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1m2VP3OdOJr4HgzWO_VjgeC9H-EXTjm8AtrbgYEu5xyI/edit?usp=drivesdk",
          "cachedResultName": "Custodian Balance Sheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "4f6108e9-8356-4658-9ca9-2b64421a5846",
      "name": "Match Accounts by Account ID",
      "type": "n8n-nodes-base.merge",
      "position": [
        2336,
        1728
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "fieldsToMatchString": "account_id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "3f2f63f4-8010-46a6-9d25-7efd94560174",
      "name": "Calculate Balance Difference",
      "type": "n8n-nodes-base.code",
      "position": [
        2560,
        1728
      ],
      "parameters": {
        "jsCode": "const TOLERANCE = 0.01;\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const d = item.json;\n  const internal = parseFloat(d.internal_balance ?? 'NaN');\n  const custodian = parseFloat(d.custodian_balance ?? 'NaN');\n  const difference = parseFloat((internal - custodian).toFixed(6));\n  const absDiff = Math.abs(difference);\n  const mismatch = isNaN(internal) || isNaN(custodian) || absDiff > TOLERANCE;\n\n  results.push({\n    json: {\n      account_id: d.account_id,\n      currency: d.currency,\n      internal_balance: internal,\n      custodian_balance: custodian,\n      difference: difference,\n      abs_difference: absDiff,\n      mismatch: mismatch,\n      checked_at: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "92daf1ee-4b9e-4bb5-a339-6b4f8c7a5f54",
      "name": "Check for Balance Mismatch",
      "type": "n8n-nodes-base.if",
      "position": [
        2784,
        1728
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4422433f-2a60-445d-a376-b8f3a1c8e42d",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.mismatch }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f9c44d76-b6a6-40fe-9587-f48bf60c4e64",
      "name": "Generate AI Mismatch Explanation",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3056,
        1648
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are a senior operations analyst at a fund administrator.\n\nAccount: {{ $json.account_id }}\nCurrency: {{ $json.currency }}\nInternal balance: {{ $json.internal_balance }}\nCustodian balance: {{ $json.custodian_balance }}\nDifference (internal minus custodian): {{ $json.difference }}\nCheck date: {{ $json.checked_at }}\n\nIn maximum 120 words, explain the most likely cause of this mismatch from: settlement delay (T+1/T+2), pending fees or accrued interest, FX conversion timing, failed corporate actions, bank charges not yet booked or data entry error. State top 1-2 causes and end with one recommended next action."
            }
          ]
        },
        "builtInTools": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "5ca9a11d-342f-43d8-9601-95ea58c83f46",
      "name": "Prepare Exception Record",
      "type": "n8n-nodes-base.code",
      "position": [
        3488,
        1664
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item, index) => {\n  const aiText = item.json.output?.[0]?.content?.[0]?.text ?? 'No explanation returned.';\n  \n  // Match by index, not hardcoded [0]\n  const original = $('Check for Balance Mismatch').all()[index].json;\n\n  return {\n    json: {\n      account_id: original.account_id,\n      currency: original.currency,\n      internal_balance: original.internal_balance,\n      custodian_balance: original.custodian_balance,\n      difference: original.difference,\n      abs_difference: original.abs_difference,\n      mismatch: original.mismatch,\n      checked_at: original.checked_at,\n      ai_explanation: aiText,\n      recon_status: 'Mismatch'\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "8986cc9c-ad55-4d4e-85c2-ed508d7250ac",
      "name": "Log Matched Records",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3104,
        1824
      ],
      "parameters": {
        "columns": {
          "value": {
            "currency": "={{ $json.currency }}",
            "mismatch": "={{ $json.mismatch }}",
            "account_id": "={{ $json.account_id }}",
            "checked_at": "={{ $json.checked_at }}",
            "difference": "={{ $json.difference }}",
            "recon_status": "Matched",
            "abs_difference": "={{ $json.abs_difference }}",
            "internal_balance": "={{ $json.internal_balance }}",
            "custodian_balance": "={{ $json.custodian_balance }}"
          },
          "schema": [
            {
              "id": "account_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "account_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "internal_balance",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "internal_balance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "custodian_balance",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "custodian_balance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "difference",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "difference",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "abs_difference",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "abs_difference",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "mismatch",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "mismatch",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "checked_at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "checked_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_explanation",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ai_explanation",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "recon_status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "recon_status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1cG_BW9jZLFphiMLuIvRj4xh3bML2Eh_KbB9Dswln3NQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1cG_BW9jZLFphiMLuIvRj4xh3bML2Eh_KbB9Dswln3NQ/edit?usp=drivesdk",
          "cachedResultName": "Reconciliation Log Sheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "531e3349-2a4e-4824-b12d-38084bd54b29",
      "name": "Edit Internal Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2112,
        1632
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "863617b6-3339-4dc1-8617-ca0a796c8fae",
              "name": "account_id",
              "type": "string",
              "value": "={{ $json.account_id }}"
            },
            {
              "id": "0d3a6dc2-30e1-4952-ad28-b025ebaba2d4",
              "name": "currency",
              "type": "string",
              "value": "={{ $json.currency }}"
            },
            {
              "id": "c89f8442-c1f5-4a3d-8084-d19a941c3e86",
              "name": "internal_balance",
              "type": "number",
              "value": "={{ Number($json.internal_balance) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "79239cec-2d78-4de7-bc06-639d82947fa4",
      "name": "Edit Custodian Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2112,
        1824
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "bcd27321-c626-4bec-a439-92628fe0d5c3",
              "name": "account_id",
              "type": "string",
              "value": "={{ $json.account_id }}"
            },
            {
              "id": "9488e96d-9628-4ea8-9f18-8cda962b479f",
              "name": "currency",
              "type": "string",
              "value": "={{ $json.currency }}"
            },
            {
              "id": "842e8f40-d406-401c-a5d6-aca09ce75206",
              "name": "custodian_balance",
              "type": "number",
              "value": "={{ Number($json.custodian_balance) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5f7b523a-720f-471e-a2e5-dbe6590ad55a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        1456
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 528,
        "content": "## Balance Comparison Logic\n\nFormats both balance sheets, matches accounts by `account_id`, calculates balance differences and detects mismatches for reconciliation."
      },
      "typeVersion": 1
    },
    {
      "id": "fc412596-8212-470a-b387-461e1fe9da91",
      "name": "Append The Data In The Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3680,
        1664
      ],
      "parameters": {
        "columns": {
          "value": {
            "currency": "={{ $json.currency }}",
            "mismatch": "={{ $json.mismatch }}",
            "account_id": "={{ $json.account_id }}",
            "checked_at": "={{ $json.checked_at }}",
            "difference": "={{ $json.difference }}",
            "recon_status": "={{ $json.recon_status }}",
            "abs_difference": "={{ $json.abs_difference }}",
            "ai_explanation": "={{ $json.ai_explanation }}",
            "internal_balance": "={{ $json.internal_balance }}",
            "custodian_balance": "={{ $json.custodian_balance }}"
          },
          "schema": [
            {
              "id": "account_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "account_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "internal_balance",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "internal_balance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "custodian_balance",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "custodian_balance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "difference",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "difference",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "abs_difference",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "abs_difference",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "mismatch",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "mismatch",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "checked_at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "checked_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_explanation",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "ai_explanation",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "recon_status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "recon_status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Vyewa8rH1WQ1d1FC9DAw7yuI6O8RRl0EcvZma6Xr4Ck/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Vyewa8rH1WQ1d1FC9DAw7yuI6O8RRl0EcvZma6Xr4Ck",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Vyewa8rH1WQ1d1FC9DAw7yuI6O8RRl0EcvZma6Xr4Ck/edit?usp=drivesdk",
          "cachedResultName": "alert_sheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "05871395-09c7-409b-912d-d84e297542e3",
  "connections": {
    "Edit Internal Fields": {
      "main": [
        [
          {
            "node": "Match Accounts by Account ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Custodian Fields": {
      "main": [
        [
          {
            "node": "Match Accounts by Account ID",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Fetch Internal Balances": {
      "main": [
        [
          {
            "node": "Edit Internal Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Custodian Balances": {
      "main": [
        [
          {
            "node": "Edit Custodian Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Exception Record": {
      "main": [
        [
          {
            "node": "Append The Data In The Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Balance Mismatch": {
      "main": [
        [
          {
            "node": "Generate AI Mismatch Explanation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Matched Records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Balance Difference": {
      "main": [
        [
          {
            "node": "Check for Balance Mismatch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Accounts by Account ID": {
      "main": [
        [
          {
            "node": "Calculate Balance Difference",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run Reconciliation on Schedule": {
      "main": [
        [
          {
            "node": "Fetch Internal Balances",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Custodian Balances",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate AI Mismatch Explanation": {
      "main": [
        [
          {
            "node": "Prepare Exception Record",
            "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.

Pro

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

About this workflow

This workflow automatically compares internal cash balances with custodian or bank balances using Google Sheets, detects mismatches by , calculates balance differences, logs matched records and sends mismatched records through OpenAI for a short explanation before saving them…

Source: https://n8n.io/workflows/15061/ — 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

AI Institutional Stock Valuation Engine with Risk Scoring & Scenario Targets

Google Sheets, XML, HTTP Request +3
AI & RAG

Overview This is a production-grade, fully automated stock analysis system built entirely in n8n. It combines institutional-level financial analysis, dual AI model consensus, and a self-improving back

Google Sheets, XML, HTTP Request +3
AI & RAG

This workflow enables the automatic and regular tracking of competitors' Instagram Reels, providing rich insights for each video (summary, topic, hook, angles, tags, etc) through ChatGPT, and storing

Google Sheets, Telegram, @Apify/N8N Nodes Apify +1
AI & RAG

Personalized Outreach & Follow-Up - Phase 2. Uses googleSheets, openAi, gmail, gmailTrigger. Scheduled trigger; 59 nodes.

Google Sheets, OpenAI, Gmail +2
AI & RAG

This automation is a complete end-to-end system designed to find, qualify, and contact B2B leads — fully automated and powered by AI. Searches for target companies on LinkedIn via Ghost Genius API, us

Google Sheets, HTTP Request, OpenAI +1