
Photo Receipt AI for Budget & Slack Alerts
Description
Categories
🤖 AI & Machine Learning
Nodes Used
n8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.cronn8n-nodes-base.slackn8n-nodes-base.slackn8n-nodes-base.webhookn8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceFree
Views0
Last Updated11/28/2025
workflow.json
{
"id": "tXIAVEIkPlrlzwO9",
"meta": {
"instanceId": "d66f4a8292e2201b622a3d783e2857e4d90c675c5e7bbd26eacc6af060964522"
},
"name": "Photo Receipt AI for Budget & Slack Alerts",
"tags": [],
"nodes": [
{
"id": "d9e934b1-3ddb-4312-8556-03e0bc75f001",
"name": "Receipt Photo Upload",
"type": "n8n-nodes-base.webhook",
"position": [
0,
256
],
"webhookId": "8d328615-3aeb-42ec-990a-1263815468b2",
"parameters": {
"path": "receipt Text",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "be58ef74-b716-4f12-875d-aa09572f9b3c",
"name": "Add to Budget Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
800,
256
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json[\"Date\"] }}",
"Items": "={{ $json[\"Items\"] }}",
"Store": "={{ $json[\"Store\"] }}",
"Amount": "={{ $json[\"Amount\"] }}"
},
"schema": [
{
"id": "Date",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Store",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Store",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Items",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Items",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Data"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
"cachedResultName": "Budget"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit?usp=drivesdk",
"cachedResultName": "Budget"
}
},
"typeVersion": 4.7
},
{
"id": "51956fcd-1422-4e1c-88fb-ea7a5f921fb2",
"name": "Code in JavaScript2",
"type": "n8n-nodes-base.code",
"position": [
1024,
256
],
"parameters": {
"jsCode": "// Get all rows from the previous Google Sheets (Read) node\nconst items = $items();\n\n// If the sheet is empty, do nothing\nif (items.length === 0) {\n return [{\n json: {\n message: 'No household budget data yet.'\n }\n }];\n}\n\n// Function to convert date strings \"YYYY/MM/DD\" or \"YYYY-MM-DD\" to Date objects\nfunction parseDate(str) {\n if (!str) return null;\n const parts = str.split(/[\\/\\-]/).map(Number);\n if (parts.length < 3) return null;\n const [y, m, d] = parts;\n return new Date(y, m - 1, d);\n}\n\n// Assume the last row is the newly added record and aggregate by its month\nconst lastRow = items[items.length - 1].json;\nconst lastDateStr = lastRow['Date'];\nconst targetDate = parseDate(lastDateStr) || new Date();\n\n// Target Year-Month (e.g., \"2025-04\")\nconst targetYm = `${targetDate.getFullYear()}-${('0' + (targetDate.getMonth() + 1)).slice(-2)}`;\n\n// Calculate total for the current month\nlet total = 0;\nfor (const { json: row } of items) {\n const ds = row['Date'];\n const d = parseDate(ds);\n if (!d) continue;\n\n const ym = `${d.getFullYear()}-${('0' + (d.getMonth() + 1)).slice(-2)}`;\n if (ym === targetYm) {\n const price = Number(row['Amount'] || 0);\n if (!isNaN(price)) {\n total += price;\n }\n }\n}\n\n// ★ Monthly budget (change this to your desired amount)\nconst budget = 30000;\n\nconst remaining = budget - total;\n\n// Assemble message for Slack\nconst lines = [\n `📊 Household Budget for ${targetYm}`,\n `Budget: ${budget.toLocaleString()} JPY`,\n `Total Spent: ${total.toLocaleString()} JPY`,\n `Remaining Budget: ${remaining.toLocaleString()} JPY`,\n];\n\nif (remaining < 0) {\n lines.push(`⚠️ You have exceeded your budget by ${Math.abs(remaining).toLocaleString()} JPY.`);\n}\n\nreturn [{\n json: {\n month: targetYm,\n budget: budget,\n totalSpent: total,\n remainingBudget: remaining,\n message: lines.join('\\n')\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "8a6fa2ef-887e-4695-94d3-7275b5dd2410",
"name": "Send a message",
"type": "n8n-nodes-base.slack",
"position": [
1600,
256
],
"webhookId": "a82d6c2e-b15c-4795-b7ec-75b5296a487b",
"parameters": {
"text": "={{ $json.output }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09SMMZGXMM",
"cachedResultName": ""
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "843ef5f2-cba2-4629-bde0-f19355739d93",
"name": "OpenRouter Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
992,
-64
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "c0ea41d0-15e8-461f-8162-f6762444f75b",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
576,
256
],
"parameters": {
"jsCode": "// Assuming AI Agent output is in $json.output\nlet raw = $json.output;\n\n// Convert to JSON if it's a string\nif (typeof raw === 'string') {\n raw = raw.trim();\n\n // Remove ```json ~ ``` if present\n raw = raw.replace(/^```(?:json)?/i, '').replace(/```$/i, '').trim();\n\n try {\n raw = JSON.parse(raw);\n } catch (error) {\n throw new Error('Could not parse AI Agent output as JSON: ' + error.message + '\\nraw=' + raw);\n }\n}\n\n// Ensure it's an array, even if only one item\nconst data = Array.isArray(raw) ? raw : [raw];\n\n// Format into n8n's items array structure\nconst items = data.map(entry => {\n return {\n json: {\n Date: entry['Date'],\n Store: entry['Store'],\n Items: entry['Items'],\n Amount: entry['Amount']\n }\n };\n});\n\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "fe796bfc-a632-4d8c-9ba3-2c76b768655d",
"name": "OpenRouter Chat Model2",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
296,
480
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "a204a1cb-3cb6-4ea0-8e79-cbd6b301cd30",
"name": "Parse Receipt",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
224,
256
],
"parameters": {
"text": "={{ $json.body[''].text }}",
"options": {
"systemMessage": "You are an expert in receipt analysis. From the following text, accurately extract the date, store name, purchased items, and amount, and output them in the following format:\n\n[\n {\n \"Date\": \"YYYY/MM/DD\",\n \"Store\": \"Store Name\",\n \"Items\": \"Item Name\",\n \"Amount\": Amount\n }\n]\n\nIf there are multiple items, return multiple objects. Do not include any extra comments or sentences, return only JSON. Group items with the same date into a single entry."
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "8515213f-2fe5-4334-bc8c-7f6b6739c85d",
"name": "Report Budget",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1248,
256
],
"parameters": {
"text": "={{ $json[\"message\"] }}",
"options": {
"systemMessage": "You are a specialist in drafting polished content for Slack. Summarize the total amount spent and the remaining budget. Example:\nCurrent spending: 1000 JPY\nRemaining budget this month: 10000 JPY"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "78cafaa5-7ef7-4441-affb-ca5494ce10eb",
"name": "Daily Report Trigger",
"type": "n8n-nodes-base.cron",
"position": [
240,
-288
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4baa9d88-2664-4dbf-b59c-d673c743564b",
"name": "Get Budget Sheet (Daily)",
"type": "n8n-nodes-base.googleSheets",
"position": [
464,
-288
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
"cachedResultName": "Budget"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
"cachedResultName": "Budget"
}
},
"typeVersion": 4.7
},
{
"id": "a1e12212-03c5-4f11-bf73-4191b7a52c41",
"name": "Monthly Analysis",
"type": "n8n-nodes-base.code",
"position": [
688,
-288
],
"parameters": {
"jsCode": "// All rows from the sheet\nconst items = $items();\nif (items.length === 0) {\n return [{\n json: {\n message: 'No household budget data yet.'\n }\n }];\n}\n\n// Date parsing function\nfunction parseDate(str) {\n if (!str) return null;\n const parts = str.split(/[\\/\\-]/).map(Number);\n if (parts.length < 3) return null;\n const [y, m, d] = parts;\n return new Date(y, m - 1, d);\n}\n\n// The month of the latest row's date will be the target month\nconst lastRow = items[items.length - 1].json;\nconst lastDate = parseDate(lastRow['Date']) || new Date();\nconst targetYm = `${lastDate.getFullYear()}-${('0' + (lastDate.getMonth() + 1)).slice(-2)}`;\n\n// For aggregation\nlet total = 0;\nconst byStore = {}; // Per store\nconst byItem = {}; // Per item\nconst byDate = {}; // Per date\n\nfor (const { json: row } of items) {\n const d = parseDate(row['Date']);\n if (!d) continue;\n\n const ym = `${d.getFullYear()}-${('0' + (d.getMonth() + 1)).slice(-2)}`;\n if (ym !== targetYm) continue; // Skip if not current month\n\n const price = Number(row['Amount'] || 0);\n if (isNaN(price)) continue;\n\n const dateKey = row['Date'];\n const store = row['Store'] || 'Unknown Store';\n const item = row['Items'] || 'Unknown Item';\n\n total += price;\n\n byDate[dateKey] = (byDate[dateKey] || 0) + price;\n byStore[store] = (byStore[store] || 0) + price;\n byItem[item] = (byItem[item] || 0) + price;\n}\n\n// Daily average\nconst days = Object.keys(byDate).length || 1;\nconst avgPerDay = Math.round(total / days);\n\n// Top N ranking function\nfunction topN(map, n) {\n return Object.entries(map)\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([key, value]) => ({\n name: key,\n amount: value\n }));\n}\n\nconst topStores = topN(byStore, 3);\nconst topItems = topN(byItem, 3);\nconst topDays = topN(byDate, 3);\n\nreturn [{\n json: {\n month: targetYm,\n monthlyTotal: total,\n dailyAverage: avgPerDay,\n top3Stores: topStores,\n top3Items: topItems,\n top3SpendingDays: topDays\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ca04ca63-5377-4f27-bc2f-87cfecee94d1",
"name": "Monthly Report",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
912,
-288
],
"parameters": {
"text": "={{`Create a report from this month's (${ $json[\"month\"] }) household budget data.\n\n- Total monthly expenditure: ${$json[\"monthlyTotal\"]} JPY\n- Daily average: ${$json[\"dailyAverage\"]} JPY\n- Top 3 stores by spending: ${JSON.stringify($json[\"top3Stores\"])}\n- Top 3 items by spending: ${JSON.stringify($json[\"top3Items\"])}\n- Top 3 days with most spending: ${JSON.stringify($json[\"top3SpendingDays\"])}\n\nSummarize this month's trends and provide simple advice in English, using bullet points and short comments. Keep it to a readable length and use a polite, informal tone.`}}",
"options": {
"systemMessage": "You are an assistant that analyzes household budget data and writes clear English reports. Based on the given numbers, describe the trends and offer simple advice. Keep it to a readable length and use a polite, informal tone."
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "6d340421-5082-4dc2-98e8-8aadb85acbc9",
"name": "Send monthly report",
"type": "n8n-nodes-base.slack",
"position": [
1264,
-288
],
"webhookId": "a82d6c2e-b15c-4795-b7ec-75b5296a487b",
"parameters": {
"text": "={{ $json.output }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09SMMZGXMM",
"cachedResultName": ""
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "1e37b78a-f1ce-46e6-94c2-3efd52f140b1",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
1320,
480
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "fc9c9473-198e-4d57-9968-f6f69030a385",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-656,
-272
],
"parameters": {
"width": 496,
"height": 592,
"content": "## What it does\nThis workflow automates your household budget tracking in several steps:\n1. **Receipt Photo Upload**: You upload a photo of your receipt to a webhook.\n2. **Parse Receipt**: An AI agent extracts the date, store name, purchased items, and total amount from the receipt text.\n3. **Add to Budget Sheet**: The extracted data is then appended to your designated Google Sheet.\n4. **Daily Budget Report**: After each receipt entry, the workflow calculates your current month's spending, remaining budget, and sends a summary to Slack.\n5. **Monthly Budget Report**: Once a day (triggered by a cron job), it reads all budget data for the current month from Google Sheets, performs an analysis (total spending, daily average, top stores, items, and spending days), and sends a comprehensive report to Slack.\n### Requirements\n- **n8n Account**: Self-hosted or Cloud.\n- **Google Sheets**: A Google Sheet set up with columns for \"Date\", \"Store\", \"Items\", and \"Amount\".\n- **Slack Workspace**: A Slack channel where you want to receive budget updates.\n- **OpenRouter Account**: An API key for OpenRouter to use their chat models."
},
"typeVersion": 1
},
{
"id": "017435d3-43cb-4322-9697-024de9511297",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
64
],
"parameters": {
"color": 7,
"width": 1600,
"height": 464,
"content": "### Steps\n1. **Google Sheets Setup**: Create/use a Google Sheet, share with n8n credentials, and select it in the workflow nodes.\n2. **OpenRouter Credentials**: Get an API key from OpenRouter and configure n8n credentials for all chat model nodes.\n3. **Slack Credentials**: Connect your Slack workspace in n8n and select your desired channels for messages.\n4. **Webhook URL**: Copy the 'Receipt Photo Upload' webhook URL after activation.\n5. **Monthly Budget Adjustment**: Edit `const budget = 30000;` in 'Code in JavaScript2' to set your budget."
},
"typeVersion": 1
},
{
"id": "53e317a2-b71a-466d-a94c-d389dccf9b83",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
288,
-496
],
"parameters": {
"color": 7,
"width": 1024,
"height": 480,
"content": "## How to customize the workflow\n\n- **Daily Report Trigger**: Adjust the cron settings for frequency.\n- **AI Model**: Change AI models in 'OpenRouter Chat Model' nodes to other LLMs.\n- **Output Formatting**: Customize Slack message content and tone in 'Report Budget' and 'Monthly Report' nodes.\n- **Additional Integrations**: Expand with more nodes for other accounting software, notifications, or advanced analysis."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "4e51c46b-3d65-4634-b5a6-ccd423cc3c4c",
"connections": {
"Parse Receipt": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Report Budget": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Monthly Report": {
"main": [
[
{
"node": "Send monthly report",
"type": "main",
"index": 0
}
]
]
},
"Monthly Analysis": {
"main": [
[
{
"node": "Monthly Report",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Add to Budget Sheet",
"type": "main",
"index": 0
}
]
]
},
"Add to Budget Sheet": {
"main": [
[
{
"node": "Code in JavaScript2",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript2": {
"main": [
[
{
"node": "Report Budget",
"type": "main",
"index": 0
}
]
]
},
"Daily Report Trigger": {
"main": [
[
{
"node": "Get Budget Sheet (Daily)",
"type": "main",
"index": 0
}
]
]
},
"Receipt Photo Upload": {
"main": [
[
{
"node": "Parse Receipt",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Report Budget",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter Chat Model1": {
"ai_languageModel": [
[
{
"node": "Monthly Report",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter Chat Model2": {
"ai_languageModel": [
[
{
"node": "Parse Receipt",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get Budget Sheet (Daily)": {
"main": [
[
{
"node": "Monthly Analysis",
"type": "main",
"index": 0
}
]
]
}
}
}