{
  "id": "Snt5iPZg9K6OAymZ",
  "meta": {
    "templateId": "voice_assistant_agent_with_telegram",
    "templateCredsSetupCompleted": true
  },
  "name": "Expense Tracker Workflow Template",
  "tags": [],
  "nodes": [
    {
      "id": "d71544a0-04a0-4a73-b0d9-e4db8852dc19",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        240,
        224
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c7f116d1-f52e-460c-ab58-1e5b44827491",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        432,
        208
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"array\",\n  \"items\": {\n    \"type\": \"object\",\n    \"required\": [\"Date\",\"Category\",\"Amount\"],\n    \"properties\": {\n      \"Date\": { \"type\":\"string\", \"pattern\":\"^\\\\d{4}-\\\\d{2}-\\\\d{2}$\" },\n      \"Category\": { \"type\":\"string\" },\n      \"Merchant\": { \"type\":[\"string\",\"null\"] },\n      \"Amount\": { \"type\":\"number\" },\n      \"Note\": { \"type\":[\"string\",\"null\"] }\n    },\n    \"additionalProperties\": false\n  }\n}\n"
      },
      "typeVersion": 1.3
    },
    {
      "id": "ead09816-98d8-442f-af2a-24ec186f4279",
      "name": "Parse Expenses with AI",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        224,
        0
      ],
      "parameters": {
        "text": "=You are Expense Parser. Your job is to read a message describing one or more expenses and return ONLY a valid JSON array of objects for a Google Sheet with columns: Date, Category, Merchant, Amount, Note.\n\nEach object must look like:\n{\n  \"Date\": \"YYYY-MM-DD\",\n  \"Category\": \"Food & Drink\" | \"Entertainment\" | \"Groceries\" | \"Transport\" | \"Fuel\" | \"Rent\" | \"Health\" | \"Shopping\" | \"Travel\" | \"Utilities\" | \"Income\" | \"Other\",\n  \"Merchant\": \"string or null\",\n  \"Amount\": number,\n  \"Note\": \"string or null\"\n}\n\nRules:\n- Date = today if not given (use {{ $now.toFormat('yyyy-MM-dd') }}).\n- Amount: numbers only (strip $ or commas).\n- Category: infer best match or \"Other\".\n- Merchant: short name if mentioned.\n- Note: short context like \"Lunch\", \"Gas\", \"Movie tickets\".\nIf no valid expenses, return [].\n\nOutput MUST be ONLY a JSON array matching the schema. No prose or code fences\nUser message: {{ $('Telegram Message Trigger').item.json.message.text }}\nAudio User Message: {{ $json.text }}",
        "batching": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "ba58f8fa-7bba-4eb4-9276-e29bbd0a9135",
      "name": "Append to Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1056,
        16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.Date }}",
            "Note": "={{ $json.Note }}",
            "Amount": "={{ $json.Amount }}",
            "Category": "={{ $json.Category }}",
            "Merchant": "={{ $json.Merchant }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Merchant",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Merchant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Note",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Note",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Date"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1008795218,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18hPbBmXjcs7Vqi2JN5b6xD-GDjMhVVedbpLitKTXHdo/edit#gid=1008795218",
          "cachedResultName": "Expenses"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "18hPbBmXjcs7Vqi2JN5b6xD-GDjMhVVedbpLitKTXHdo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18hPbBmXjcs7Vqi2JN5b6xD-GDjMhVVedbpLitKTXHdo/edit?usp=drivesdk",
          "cachedResultName": "Expense Tracker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "d65ced97-b390-4c65-9f0c-d62100c27abd",
      "name": "Wait 0.5 seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        1264,
        16
      ],
      "parameters": {
        "amount": 0.5
      },
      "typeVersion": 1.1
    },
    {
      "id": "b4b6cd46-e379-4384-9a9c-84a133258a0c",
      "name": "Send Telegram Confirmation",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1968,
        -32
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "={{ $('Telegram Message Trigger').first().json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a53bba6f-067f-4f5a-8ecb-43c13b1cbe10",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        576,
        0
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "output"
      },
      "typeVersion": 1
    },
    {
      "id": "bebffb6b-fba5-4cc1-b2fc-d2e411a0322b",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        848,
        0
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "f96ba471-26c0-49d7-a598-75c7250970c7",
      "name": "Telegram Message Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -928,
        16
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "download": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "93c9787e-0762-4353-acc7-ad1af68c1ad6",
      "name": "Check if Audio file",
      "type": "n8n-nodes-base.if",
      "position": [
        -704,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "2b538077-d3d3-4713-b973-68748313ff97",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.message.voice }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1e00fabd-b1f6-47b9-9ea6-98752e8606b7",
      "name": "Transcribe audio",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -256,
        -80
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "d79a073d-16b5-47c3-a5c2-309467909892",
      "name": "Set field",
      "type": "n8n-nodes-base.set",
      "position": [
        -256,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "eb912219-2436-4f04-8ffc-c1c20eb07344",
              "name": "text",
              "type": "string",
              "value": "={{ $json.message.text }}"
            },
            {
              "id": "ded90f7a-bd44-4648-a9eb-5c16d2356adb",
              "name": "",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "789e3937-56ae-4184-9bff-756e28e8a49d",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1456,
        -32
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 3.2
    },
    {
      "id": "7b9b778b-67ed-4f7b-a9cd-ce3a99187512",
      "name": "Get File",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -480,
        -80
      ],
      "parameters": {
        "fileId": "={{ $json.message.voice.file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7a049246-8692-4ca2-92d7-6044ce0f5fe8",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        -624
      ],
      "parameters": {
        "color": 3,
        "width": 416,
        "height": 208,
        "content": "### \ud83d\udee0\ufe0f Troubleshooting  \n- **Missing rows?** Increase the Wait time to 800\u20131200 ms.  \n- **AI returns no expenses?** Check that your message includes numbers and merchants.  \n- **Google Sheets errors?** Confirm spreadsheet ID, sheet name, and column mapping.  \n- **Audio not transcribing?** Ensure Whisper is selected in the transcription node.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "cec5f58e-0599-4885-ad84-3c5fc2a136e5",
      "name": "Build Expense Summary Text",
      "type": "n8n-nodes-base.code",
      "position": [
        864,
        -160
      ],
      "parameters": {
        "jsCode": "// Build one message from all Split Out items\nconst items = $input.all().map(i => i.json);\nconst lines = items.map(e =>\n  `${e.Date} \u2014 ${e.Category} $${Number(e.Amount).toFixed(2)}${e.Merchant ? ' ('+e.Merchant+')' : ''}`\n);\nconst count = items.length;\nreturn [{\n  json: {\n    text: `Just tracked ${count} expense${count!==1?'s':''}:\\n` + lines.join('\\n')\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e63af906-3ac2-4b91-a528-aa82b3bc0a21",
      "name": "Build Confirmation Text",
      "type": "n8n-nodes-base.set",
      "position": [
        1664,
        -32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a029bf2c-7ba9-4114-a679-bc68f04f7030",
              "name": "text",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3bae6da9-7466-4b8d-99bc-40ff66f37441",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1760,
        0
      ],
      "parameters": {
        "width": 672,
        "height": 288,
        "content": "### \ud83d\udd27 Setup Instructions  \n1. Connect credentials: **Telegram**, **Google**, **OpenAI**.  \n2. Create a Google Sheet with headers:  \n   `Date | Category | Merchant | Amount | Note`  \n3. Copy your Sheet ID + Sheet name.  \n4. Map the columns correctly in the **Append to Google Sheet** node.  \n5. Pick models:  \n   - Chat model: `gpt-4o-mini` (recommended)  \n   - Audio transcription: Whisper (for voice notes)  \n6. Keep the **Wait** node at `500\u20131000 ms` to avoid API race conditions.  \n7. Test by sending a message like:  \n   `Gas 34.67, Groceries 82.45, Coffee 6.25, Lunch 14.90`\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e9678959-c7f9-4cff-9f29-065fee86fc6a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1760,
        -624
      ],
      "parameters": {
        "width": 672,
        "height": 624,
        "content": "### \ud83d\udccc Purpose  \nLog multiple expenses directly from Telegram into Google Sheets \u2014 using either text or audio messages.\n\nThis workflow is for anyone who wants a **fast, low-effort way to track expenses** without opening a spreadsheet or app. You simply send a quick message (or voice note) like:\n\n`Groceries 82.45, Coffee 6.25, Uber 14.90`\n\nand each item is automatically parsed and saved as its own row.\n\n**Who this helps:**  \n- Busy entrepreneurs  \n- Freelancers and contractors  \n- People who track business expenses manually  \n- Anyone who prefers capturing expenses on the go  \n- Users who don\u2019t want to build a full accounting system\n\n**Why this is useful:**  \n- It replaces the repetitive task of entering expenses manually  \n- Voice notes let you log hands-free while driving or moving  \n- AI extracts multiple expenses from a single message, saving time  \n- Google Sheets keeps your data accessible, editable, and exportable  \n- Works instantly from your phone through Telegram\n\n**Why download this template:**  \nIt gives you a ready-made, reliable system for expense tracking with **zero manual data entry**, using AI and simple messaging. No accounting software needed \u2014 just Telegram and a Google Sheet.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0f45caec-6049-4a2b-bf3a-ff39cc305fc8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 608,
        "content": "### \ud83c\udfa4 Telegram Input & Transcription  \nThis section receives a Telegram message, detects if it's audio, and transcribes it when needed.\n\nWhy: We want the user to be able to log expenses hands-free through **text or voice**, so both inputs flow into the same text-processing path.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d25f9ad8-c964-4c49-a02e-d92316937094",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 640,
        "content": "### \ud83e\udd16 AI Parsing & Expense Extraction  \nThis section uses AI to identify all expenses in the message, splits them into individual items, and prepares them for Google Sheets.\n\nWhy: A single message may contain several expenses \u2014 AI helps extract structured fields reliably.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "205d3995-05cd-491b-a9b6-3c7d7229e12f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        -336
      ],
      "parameters": {
        "color": 7,
        "width": 1408,
        "height": 688,
        "content": "### \ud83d\udcca Write to Sheets & Send Confirmation  \nThis section loops through each expense, writes it to Google Sheets, builds a summary, and sends a confirmation back to Telegram.\n\nWhy: Users need quick feedback that everything was recorded correctly.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "8becffad-c320-4615-b121-563074fa866d",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Build Confirmation Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get File": {
      "main": [
        [
          {
            "node": "Transcribe audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set field": {
      "main": [
        [
          {
            "node": "Parse Expenses with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Expense Summary Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Append to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe audio": {
      "main": [
        [
          {
            "node": "Parse Expenses with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 0.5 seconds": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Parse Expenses with AI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check if Audio file": {
      "main": [
        [
          {
            "node": "Get File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set field",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Google Sheet": {
      "main": [
        [
          {
            "node": "Wait 0.5 seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Expenses with AI": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Confirmation Text": {
      "main": [
        [
          {
            "node": "Send Telegram Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Parse Expenses with AI",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Message Trigger": {
      "main": [
        [
          {
            "node": "Check if Audio file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Expense Summary Text": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Telegram Confirmation": {
      "main": [
        []
      ]
    }
  }
}