{
  "id": "tNM1ijEZ8gWfVxaM",
  "name": "AutoBudgetTelegram+Firefly",
  "tags": [
    {
      "id": "CJEsjBkYVWZdgNZa",
      "name": "LLM",
      "createdAt": "2025-11-28T18:56:28.956Z",
      "updatedAt": "2025-11-28T18:56:28.956Z"
    },
    {
      "id": "NPouo2sN6F8nYIw1",
      "name": "Telegram",
      "createdAt": "2025-11-28T18:56:31.687Z",
      "updatedAt": "2025-11-28T18:56:31.687Z"
    },
    {
      "id": "kXS4WSOIuW9HglP2",
      "name": "Templates",
      "createdAt": "2025-11-28T18:56:22.483Z",
      "updatedAt": "2025-11-28T18:56:22.483Z"
    },
    {
      "id": "nvgXicJYeK7nKBBf",
      "name": "HTTP",
      "createdAt": "2025-11-28T18:56:35.744Z",
      "updatedAt": "2025-11-28T18:56:35.744Z"
    },
    {
      "id": "ujpwDAZzATL8pDYI",
      "name": "Budget",
      "createdAt": "2025-11-28T19:18:18.314Z",
      "updatedAt": "2025-11-28T19:18:18.314Z"
    }
  ],
  "nodes": [
    {
      "id": "b840d397-569c-4819-a635-f01e3f2cd57d",
      "name": "Get file from server",
      "type": "n8n-nodes-base.telegram",
      "position": [
        736,
        688
      ],
      "parameters": {
        "fileId": "={{ $json.message.document.file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "dce037ba-70e5-48c8-acb6-802f6e1a0e65",
      "name": "Analyze the image or pdf",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        960,
        496
      ],
      "parameters": {
        "text": "## Role\nExtract transaction data as numbered list. Each transaction = separate line with criteria as sub-items.\n\n## Rules\nCriteria to extract:\n1. Billing Amount\n2. Transaction Date\n3. Payee\n4. Bank ID\n\n## Firefly III Bank ID\nBank ID = [123]\n\n## Image Processing (image/jpeg only)\nUse the following template  to identify the criteria:\n\n'Online Purchase\nCard: Visa\nAt: (3. Payee)\nTotal due amount: (1. Billing Amount)\nDate: (2. Transaction Date)'",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "options": {},
        "resource": "document",
        "simplify": false,
        "inputType": "binary"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "4ab87fc0-ebde-4e68-85d8-5b689d33e917",
      "name": "Make it pretty",
      "type": "n8n-nodes-base.code",
      "position": [
        1536,
        496
      ],
      "parameters": {
        "jsCode": "// n8n Code node - Transform transaction data\n\nconst data = $input.item.json;\n\nconst output = [];\n\nif (data.output && data.output.Transaction && Array.isArray(data.output.Transaction)) {\n  data.output.Transaction.forEach(transaction => {\n    output.push({\n      \"Type\": transaction.Type || \"\",\n      \"Date\": transaction.Date || \"\",\n      \"Payee\": transaction.Payee || \"\",\n      \"Amount\": transaction.Amount || \"\",\n      \"Account\": transaction.Account || \"\",\n      \"Description\": transaction.Description || \"\",\n      \"Category\": transaction.Category || \"\"\n    });\n  });\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "d755e1a4-bb53-4f7c-a650-a0c2c9fc142a",
      "name": "Get image from server",
      "type": "n8n-nodes-base.telegram",
      "position": [
        736,
        496
      ],
      "parameters": {
        "fileId": "={{ $json.message.photo[1].file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6386bc2a-d0b8-43f3-8f9e-3dc351301c66",
      "name": "Post to Firefly",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2000,
        608
      ],
      "parameters": {
        "url": "http[s]://[insert firefly url here]/api/v1/transactions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"transactions\": [\n    {\n      \"type\": \"{{ $('Make it pretty').item.json.Type }}\",\n      \"date\": \"{{ $json.Date }}\",\n      \"amount\": \"{{ $json.Amount }}\",\n      \"description\": \"{{ $json.Description }}\",\n      \"category_name\": \"{{ $json.Category }}\",\n      \"source_id\": {{ $json.Account }},\n      \"destination_name\": \"{{ $json.Payee }}\"\n    }\n  ]\n} ",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "Content-Type",
              "value": "=application/json"
            }
          ]
        }
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "6f1db2c1-b443-4c7c-a01a-7fc55091d794",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        480,
        624
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Image",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "7c55453f-4e09-49d4-a641-f18c4f4b4ccf",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.message.photo[0] }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "PDF",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "23528a51-6952-4243-83a2-1c6f506e2252",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.message.document }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Report",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "75acbc61-4e26-45da-8575-f56a76e01074",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "Report"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "report",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "99beefa2-a758-462e-bab6-a285d3b1ebe8",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "report"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "07b2493d-dec0-4bcd-af0d-1ba3c94d9e71",
      "name": "Analyzer Scraper",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1184,
        720
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "66c372e2-7b0d-461c-8e23-5fa159c0ce8f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        864
      ],
      "parameters": {
        "color": 7,
        "width": 1152,
        "height": 256,
        "content": "## 4. Bonus: Report Generation\nProduces a budget report from the beginning of the month to now if 'report' is sent to the telegram chat"
      },
      "typeVersion": 1
    },
    {
      "id": "cd42b9d9-fca9-4e4b-8277-8f60cad4cdc2",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 464,
        "content": "## 2. Image & PDF Processing\nPulls the image or document from Telegram servers. Gemini analyzes and parses the relevant info"
      },
      "typeVersion": 1
    },
    {
      "id": "114a012f-2a4c-42e5-aaf5-28815677f973",
      "name": "Loop for Multiple Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1760,
        496
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "ca8be60f-4fcf-49be-bde2-a944feeea88a",
      "name": "Confirmation Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2000,
        448
      ],
      "parameters": {
        "text": "=Transaction(s) recorded",
        "chatId": "={{ $('Transaction Image or PDF Received').item.json.message.chat.id }}",
        "forceReply": {},
        "replyMarkup": "forceReply",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.2
    },
    {
      "id": "cc18d851-e81d-4215-87b0-0c65f3272210",
      "name": "Get Remaining Budget",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        736,
        960
      ],
      "parameters": {
        "url": "http[s]://[insert firefly url here]/api/v1/budget-limits",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "queryParameters": {
          "parameters": [
            {
              "name": "start",
              "value": "={{ $now.year }}-{{ $now.month }}-01"
            },
            {
              "name": "end",
              "value": "={{ $now.year }}-{{ $now.month }}-{{ $now.day }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "Content-Type",
              "value": "application/vnd.api+json"
            }
          ]
        }
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ab9ada0f-31e5-4b7c-b29d-108670b60217",
      "name": "Parse JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        960,
        960
      ],
      "parameters": {
        "jsCode": "// Get the input data - handle different possible structures\nconst items = $input.all();\nlet inputData;\n\n// Try to find the data in different possible locations\nif (items[0].json[0]) {\n  inputData = items[0].json[0];\n} else if (items[0].json) {\n  inputData = items[0].json;\n} else if (Array.isArray(items[0])) {\n  inputData = items[0][0];\n} else {\n  inputData = items[0];\n}\n\n// Check if we have the expected structure\nif (!inputData.included || !inputData.data) {\n  // Return the structure so you can see what you're receiving\n  return [{\n    error: \"Data structure not as expected\",\n    receivedKeys: Object.keys(inputData),\n    sampleData: inputData\n  }];\n}\n\n// Extract budget data with spending info\nconst budgets = inputData.included;\nconst budgetLimits = inputData.data;\n\n// Create a map of budget spending from limits data\nconst spendingMap = {};\nbudgetLimits.forEach(limit => {\n  const budgetId = limit.attributes.budget_id;\n  const spent = limit.attributes.spent;\n  const spentAmount = spent.length > 0 ? parseFloat(spent[0].sum) : 0;\n  const pcAmount = parseFloat(limit.attributes.pc_amount);\n  const pcSpent = limit.attributes.pc_spent.length > 0 ? Math.abs(parseFloat(limit.attributes.pc_spent[0].sum)) : 0;\n  \n  spendingMap[budgetId] = {\n    spent: Math.abs(spentAmount),\n    pcAmount: pcAmount,\n    pcSpent: pcSpent\n  };\n});\n\n// Transform into structured array\nconst parsedData = budgets.map(budget => {\n  const attrs = budget.attributes;\n  const budgetId = budget.id;\n  const pcAmount = parseFloat(attrs.pc_auto_budget_amount);\n  const pcSpent = spendingMap[budgetId]?.pcSpent || 0;\n  const pcRemaining = pcAmount - pcSpent;\n  const percentUsed = amount > 0 ? ((spent / amount) * 100).toFixed(1) : 0;\n  \n  return {\n    budgetCategory: attrs.name,\n    percentUsed: parseFloat(percentUsed),\n    pcAmount: parseFloat(pcAmount.toFixed(2)),\n    pcSpent: parseFloat(pcSpent.toFixed(2)),\n    pcRemaining: parseFloat(pcRemaining.toFixed(2)),\n    order: attrs.order,\n    pcCurrencyCode: attrs.primary_currency_code,\n    pcCurrencySymbol: attrs.primary_currency_symbol\n  };\n});\n\n// Sort by budget order\nparsedData.sort((a, b) => a.order - b.order);\n\n// Calculate totals\nconst totals = parsedData.reduce((acc, item) => {\n  acc.pcAmount += item.pcAmount;\n  acc.pcSpent += item.pcSpent;\n  acc.pcRemaining += item.pcRemaining;\n  return acc;\n}, { amount: 0, spent: 0, remaining: 0, pcAmount: 0, pcSpent: 0, pcRemaining: 0 });\n\n// Calculate overall percentage\nconst totalPercentUsed = totals.amount > 0 ? ((totals.spent / totals.amount) * 100).toFixed(1) : 0;\n\n// Add totals row\nparsedData.push({\n  budgetCategory: 'TOTAL',\n  percentUsed: parseFloat(totalPercentUsed),\n  pcAmount: parseFloat(totals.pcAmount.toFixed(2)),\n  pcSpent: parseFloat(totals.pcSpent.toFixed(2)),\n  pcRemaining: parseFloat(totals.pcRemaining.toFixed(2)),\n  order: 999,\n});\n\nreturn parsedData;"
      },
      "typeVersion": 2
    },
    {
      "id": "37e61de9-924c-4786-a50f-4abab4b0b4bc",
      "name": "Pull required fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1184,
        960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "37f92d9d-4f7e-4fc1-8f22-7486315ac3ca",
              "name": "Bucket",
              "type": "string",
              "value": "={{ $json.budgetCategory }}"
            },
            {
              "id": "4e90b4da-c1a5-49e4-b584-0d5d75e86b29",
              "name": "Budget",
              "type": "number",
              "value": "={{ $json.pcAmount }}"
            },
            {
              "id": "361f590f-5502-4e6c-91c6-dca776aecead",
              "name": "Spent",
              "type": "number",
              "value": "={{ $json.pcSpent }}"
            },
            {
              "id": "7ce4bb41-473c-4679-9380-94a287ffe768",
              "name": "Remaining",
              "type": "number",
              "value": "={{ $json.pcRemaining }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "22624e96-30d2-4251-a114-dd057749a605",
      "name": "Make CSV",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        1408,
        960
      ],
      "parameters": {
        "options": {
          "fileName": "=Budget report for {{ $now.monthLong }} 01 to {{ $now.day }}.csv"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "c6f611a6-d37b-4bf3-bad3-4497a86af766",
      "name": "Send CSV",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1632,
        960
      ],
      "parameters": {
        "chatId": "={{ $('Transaction Image or PDF Received').item.json.message.chat.id }}",
        "operation": "sendDocument",
        "binaryData": true,
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.2
    },
    {
      "id": "7ae2a7f0-eb96-4210-8e23-e3f962417c45",
      "name": "Transaction Image or PDF Received",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        256,
        656
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "71222495-5178-441b-87e2-66ce43ad74bd",
      "name": "Parse details & add category",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1184,
        496
      ],
      "parameters": {
        "text": "={{ $json.candidates[0].content.parts[0].text }}\n\nHere is the transaction description(s): {{ $('Transaction Image or PDF Received').item.json.message.caption }}",
        "options": {
          "systemMessage": "=## Role\nParse transactions into structured components: Billing Amount, Payee, Transaction Date (yyyy-mm-dd), Bank ID, Category, Transaction Type, Description.\n\n## Rules\n- Remove currency symbols\n- Year is always 2025+\n\nTransaction Type:\n- withdrawal = purchases/spending\n- deposit = salary/income/interest\n- transfer = internal transfers\n\nDescription: \n- If numbered descriptions = multiple transactions (in order)\n- Not numbered = single transaction\n- If no description, identify number of transactions and create a description for each\n\nCategories (select one):\nTaxi, Phone & Telecom, Pet Expenses, Food Delivery, Groceries, Home Goods, Electronics, Health, Makeup, Clothes, Beauty & Salon, Car Expenses, Holidays, Coffee, Entertainment & Going Out, Rent, Salary, Cashback & Interest, Family Transfer\n\nCategory notes:\n- Health = medicines, mental health, doctor visits\n- Car Expenses = car-related expenses including gas/petrol\n\n#Special overrides:\n#- Include if necessary or delete\n\n#Deposit overrides:\n#- Include if necessary or delete"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "56c34be1-e6c5-46b3-ba3b-4a14ca9c6779",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        384
      ],
      "parameters": {
        "width": 752,
        "height": 720,
        "content": "# Automated Transaction Posting in Firefly III\n\n## How it works\n1. **Send a transaction** image (screenshot) or pdf statement to Telegram\n2. Gemini Analyzes and Parses the transaction data\n3. Relevant info is structured and posted to Firefly III\n4. Bonus: sending the word 'Report' to the Telegram channel creates a budget report of the month-to-date\n\n## Setup\n- [ ] Go into each Telegram node and either create or select the Telegram credential you want to use\n- [ ] Go into each HTTP node and \n  - under `OAuth2 API` select or create your Firefly OAuth2 credential. Use this link to find out how to create one if necessary https://docs.firefly-iii.org/how-to/firefly-iii/features/api/#create-an-oauth2-client\n  - insert your Firefly domain ONLY where it says `[insert firefly url here]`\n  - remove the bracket around `s` in `http[s]` if using a secure connection, or remove the `[s]` if using a local/unsecured http connection\n- [ ] Go into the `Analyze the image or pdf` node and select or create your Gemini credential and then select the model you wish to use\n- [ ] Go into the `Analyzer Scraper` node and select or create your Gemini credential and then select the model you wish to use\n\n\n## Customization\n- [ ] modify the rules in the 'Analyze the image or pdf' as relates to your Firefly account. At the least, provide Bank ID as per your asset account in Firefly (if you click on your asset account, the ID number is the 2 or 3 digit number in the URL)\n- [ ] Go into the `Parse details & add category` node\n  - modify the 'Categories' area to match your needs\n  - add any special overrides as needed (i.e. if 'Payee = UBER', set 'Category' to 'Transportation') or delete\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d99b0e30-8550-4fa5-946b-ac79fd05ddf2",
      "name": "Parse Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1328,
        720
      ],
      "parameters": {
        "jsonSchemaExample": "{\n\t\"Transaction\": [\n      {\n        \"Type\": \"Transaction Type\",\n        \"Amount\": \"Billing Amount (SAR)\",\n        \"Payee\": \"Payee\",\n        \"Date\": \"Transaction Date\",\n        \"Account\": \"Bank ID\",\n        \"Description\": \"Description\",\n        \"Category\": \"None\"\n      }\n    ]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "9a206719-7243-4ad7-8ebd-a8d801116a97",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 464,
        "content": "## 3. Formatting and posting\nThe info is formatted to fit the Firefly III requirements. Each transaction is posted 1 by 1, with a confirmation of each record sent back to the Telegram channel"
      },
      "typeVersion": 1
    },
    {
      "id": "9e5ad62b-f75e-4ada-82b4-a18b16ac03c2",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        528
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 336,
        "content": "## 1. Manual Trigger\nImage, PDF or Text sent by user to Telegram channel\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "97401c0a-d777-4aee-b543-43eaa80c5483",
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Get image from server",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get file from server",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Remaining Budget",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Remaining Budget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make CSV": {
      "main": [
        [
          {
            "node": "Send CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON": {
      "main": [
        [
          {
            "node": "Pull required fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Output": {
      "ai_outputParser": [
        [
          {
            "node": "Parse details & add category",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Make it pretty": {
      "main": [
        [
          {
            "node": "Loop for Multiple Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Firefly": {
      "main": [
        [
          {
            "node": "Loop for Multiple Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyzer Scraper": {
      "ai_languageModel": [
        [
          {
            "node": "Parse details & add category",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get Remaining Budget": {
      "main": [
        [
          {
            "node": "Parse JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get file from server": {
      "main": [
        [
          {
            "node": "Analyze the image or pdf",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pull required fields": {
      "main": [
        [
          {
            "node": "Make CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get image from server": {
      "main": [
        [
          {
            "node": "Analyze the image or pdf",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop for Multiple Items": {
      "main": [
        [
          {
            "node": "Confirmation Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post to Firefly",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze the image or pdf": {
      "main": [
        [
          {
            "node": "Parse details & add category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse details & add category": {
      "main": [
        [
          {
            "node": "Make it pretty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transaction Image or PDF Received": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}