This workflow follows the Agent → Datatable 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 →
{
"name": "zettel-ocr",
"nodes": [
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {
"download": false,
"userIds": "1542517546"
}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
0,
432
],
"id": "795714e2-f221-49f8-be30-013f345c1a29",
"name": "Telegram Trigger",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "file",
"fileId": "={{ $json.message.photo[2].file_id }}",
"additionalFields": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
224,
432
],
"id": "434f74a4-cd74-4fdc-a4cd-804ac2ff1903",
"name": "Get a file",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "image",
"operation": "analyze",
"modelId": {
"__rl": true,
"value": "models/gemini-3-flash-preview",
"mode": "list",
"cachedResultName": "models/gemini-3-flash-preview"
},
"text": "Du bist ein hochpr\u00e4ziser Assistent f\u00fcr die Datenextraktion. Deine Aufgabe ist es, den angeh\u00e4ngten Kassenbon auszulesen.\n\nExtrahiere alle gekauften Artikel, deren Preise und ordne jeden Artikel einer passenden Kategorie zu.\nInterpretiere den internen Namen des Produktes in ein menschenlesbares Format.\n\nW\u00c4HLE F\u00dcR \"category\" AUSSCHLIESSLICH AUS DIESER LISTE:\n\nLebensmittel\n\nS\u00fc\u00dfwaren\n\nGetr\u00e4nke\n\nAlkohol\n\nDrogerie & Haushalt\n\nPfand/Leergut\n\nSonstiges\n\nWICHTIG F\u00dcR DIE AUSGABE:\n\nGib das Ergebnis AUSSCHLIESSLICH als valides JSON-Array aus.\n\nF\u00fcge absolut keinen einleitenden oder abschlie\u00dfenden Text hinzu.\n\nVerwende KEINE Markdown-Bl\u00f6cke (wie ```json). Beginne direkt mit der eckigen Klammer [ und ende mit ].\n\nDie Preise (\"cost\") m\u00fcssen als Zahlen (Floats) formatiert sein, nicht als Text (also 2.39 statt \"2.39\"). Beachte auch negative Werte bei R\u00fcckgaben/Leergut.\n\nVerwende exakt dieses Format als Vorlage f\u00fcr deine Ausgabe:\n[\n{\n\"item\": \"BE Junge Erbsen 1 kg\",\n\"interpretation\": \"Erbsen\",\n\"cost\": 2.39,\n\"category\": \"Lebensmittel\"\n},\n{\n\"item\": \"Frosch WM Aloe Vera 25WL\",\n\"interpretation\": \"Waschmittel\",\n\"cost\": 2.49,\n\"category\": \"Drogerie & Haushalt\"\n},\n{\n\"item\": \"JackDaniels ColaZero0,33L\",\n\"interpretation\": \"Jack Danies Cola\",\n\"cost\": 1.99,\n\"category\": \"Alkohol\"\n},\n{\n\"item\": \"Einwegpfand 19%\",\n\"interpretation\": \"Pfand,\n\"cost\": 0.25,\n\"category\": \"Pfand/Leergut\"\n}\n]",
"inputType": "binary",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"typeVersion": 1.1,
"position": [
448,
432
],
"id": "8eec2eeb-673e-4721-b7d4-ef1806c6aae1",
"name": "Analyze an image",
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.content.parts[0].text }}",
"hasOutputParser": true,
"options": {
"systemMessage": "Stelle sicher, dass der Input dem angeforderten Schema entspricht."
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
704,
432
],
"id": "5faee37a-9736-4bcf-95ad-bf467e453105",
"name": "AI Agent"
},
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": {\n \"type\": \"string\",\n \"description\": \"Der Name des gekauften Artikels\"\n },\n \"interpretation\": {\n \"type\": \"string\",\n \"description\": \"Menschenlesbare Interpretation des Produktnames\"\n },\n \"cost\": {\n \"type\": \"number\",\n \"description\": \"Der Preis des Artikels als Zahl\"\n },\n \"category\": {\n \"type\": \"string\",\n \"description\": \"Die Kategorie des Artikels\"\n}\n },\n \"required\": [\"item\", \"cost\", \"category\"]\n }\n}",
"autoFix": true
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
800,
656
],
"id": "a6d33de3-ba68-4e66-909f-371b535b7bad",
"name": "Structured Output Parser"
},
{
"parameters": {
"model": "anthropic/claude-haiku-4.5",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
880,
864
],
"id": "68cbe6fc-acec-4834-8bb9-248c7ba86331",
"name": "OpenRouter Chat Model",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": "mistralai/mistral-medium-3.1",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
672,
656
],
"id": "988b8b2e-a854-4b27-8c3f-fbabe3ae4e62",
"name": "OpenRouter Chat Model1",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "sendAndWait",
"chatId": "1542517546",
"message": "={{ $('Code in JavaScript').item.json.text }}\n\nTotal: {{ $('Code in JavaScript').item.json.total }}",
"approvalOptions": {
"values": {
"approvalType": "double",
"disapproveLabel": "\ud83d\udcdc Edit"
}
},
"options": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1840,
432
],
"id": "f4ce0107-bfc2-4efa-8da8-74211fafdab6",
"name": "Send message and wait for response",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nlet execId = $execution.id;\nlet text = `\ud83e\uddfe **Neuer Kassenbon erkannt!**\\n`;\nlet total = 0;\nlet html = `<h2>Kassenbon korrigieren</h2>\n <form action=\"https://beleriand.tailf188d3.ts.net/webhook/zettel-save\" method=\"POST\">\n <input type=\"hidden\" name=\"receipt_id\" value=\"${execId}\">`\n\nfor (let i = 0; i < $input.first().json.output.length; i++) {\n let item = $input.first().json.output[i];\n const addText = `\n\ud83d\uded2 **${item.item}**\n\ud83d\udcb6 ${item.cost} \u20ac | \ud83c\udff7\ufe0f ${item.category}\n`\n text += addText\n total += item.cost\n\n html += `<div style=\"margin-bottom: 10px;\">\n <input type=\"text\" name=\"item_${i}\" value=\"${item.item}\">\n <input type=\"text\" name=\"interpretation_${i}\" value=\"${item.interpretation}\">\n <input type=\"number\" step=\"0.01\" name=\"cost_${i}\" value=\"${item.cost}\">\n <input type=\"text\" name=\"category_${i}\" value=\"${item.category}\">\n \n </div>`;\n}\n\nhtml += `<button type=\"submit\">\u00c4nderungen speichern</button></form>`;\n\nreturn {text, total, html}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1168,
432
],
"id": "ef72b2c1-9eef-4859-9480-d7640e3e17f3",
"name": "Code in JavaScript"
},
{
"parameters": {
"html": "<!DOCTYPE html>\n\n<html>\n<head>\n <meta charset=\"UTF-8\" />\n <title>My HTML document</title>\n</head>\n<body>\n <div class=\"container\">\n {{ $json.html }}\n </div>\n\n\n<style>\n html {\n color-scheme: dark;\n }\n.container {\n text-align: center;\n padding: 16px;\n border-radius: 8px;\n}\n\nh1 {\n font-size: 24px;\n font-weight: bold;\n padding: 8px;\n}\n\nh2 {\n font-size: 18px;\n font-weight: bold;\n padding: 8px;\n}\n</style>\n\n</body>\n</html>"
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [
1392,
432
],
"id": "e8b14f50-e295-4f11-9e80-ac46b3e4df7b",
"name": "HTML"
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"value": "TrgvcHqyI6Aq2ZIU",
"mode": "list",
"cachedResultName": "kassenzettel",
"cachedResultUrl": "/projects/XJIjlMZFCxfRf2Fm/datatables/TrgvcHqyI6Aq2ZIU"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"zettelhtml": "={{ $json.html }}",
"zettelid": "={{ $execution.id }}"
},
"matchingColumns": [
"zettelhtml"
],
"schema": [
{
"id": "zettelhtml",
"displayName": "zettelhtml",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "zettelid",
"displayName": "zettelid",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1616,
432
],
"id": "cbf0e56b-322e-4587-abb1-65cb654a6fff",
"name": "Insert row"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "83b52001-f1e8-4359-abea-e4de740c40c5",
"leftValue": "={{ $json.data.approved }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2064,
432
],
"id": "17ef1508-8696-47b4-81d9-8b8b754f8562",
"name": "If"
},
{
"parameters": {
"chatId": "1542517546",
"text": "=https://beleriand.tailf188d3.ts.net/webhook/faae630f-84bf-4a36-86a8-3f1e33f20d1e/zettel-edit/{{ $execution.id }}",
"additionalFields": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
2288,
528
],
"id": "2cc6ab95-93ff-4446-a8c8-f54e93dab6fa",
"name": "Send a text message",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "1R4VH6g02lEJhxYuVqN7wcBmel0ogJsRMuIMstNEPEO8",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "https://docs.google.com/spreadsheets/d/1R4VH6g02lEJhxYuVqN7wcBmel0ogJsRMuIMstNEPEO8/edit?gid=0#gid=0",
"mode": "url"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"item": "={{ $json.item }}",
"cost": "={{ $json.cost }}",
"category": "={{ $json.category }}",
"date": "={{ $now }}",
"zettelid": "={{ $('Insert row').item.json.zettelid }}",
"day": "={{ $now.toFormat('yyyy-MM-dd') }}",
"month": "={{ $now.toFormat('yyyy-MM') }}",
"interpretation": "={{ $json.interpretation }}"
},
"matchingColumns": [],
"schema": [
{
"id": "zettelid",
"displayName": "zettelid",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "interpretation",
"displayName": "interpretation",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "item",
"displayName": "item",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "cost",
"displayName": "cost",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "category",
"displayName": "category",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "date",
"displayName": "date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "day",
"displayName": "day",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "month",
"displayName": "month",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"useAppend": true
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
2736,
336
],
"id": "2fff9d79-c1ac-4428-abdf-79bf26b6aa2d",
"name": "Append row in sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "3bfcac38-bf34-4bf5-a460-e48886cc44a9",
"name": "output",
"value": "={{ $('AI Agent').item.json.output }}",
"type": "array"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2288,
336
],
"id": "aa0a0047-a523-4adb-b94e-9e9a5397efb7",
"name": "Edit Fields"
},
{
"parameters": {
"fieldToSplitOut": "output",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
2512,
336
],
"id": "dab36c2d-08de-47ab-b9fb-13765f092f93",
"name": "Split Out"
}
],
"connections": {
"Telegram Trigger": {
"main": [
[
{
"node": "Get a file",
"type": "main",
"index": 0
}
]
]
},
"Get a file": {
"main": [
[
{
"node": "Analyze an image",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Structured Output Parser",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Analyze an image": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
},
"Send message and wait for response": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"HTML": {
"main": [
[
{
"node": "Insert row",
"type": "main",
"index": 0
}
]
]
},
"Insert row": {
"main": [
[
{
"node": "Send message and wait for response",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
],
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Append row in sheet",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "0e82e404-eb24-4cad-a792-f13b12834ffe",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "7UJ9BU2UHgfAezsB",
"tags": [
{
"updatedAt": "2026-03-22T10:40:47.895Z",
"createdAt": "2026-03-22T10:40:47.895Z",
"id": "Kx6dMYMFRgBgbM5c",
"name": "kassenzettel"
}
]
}
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.
googlePalmApigoogleSheetsOAuth2ApiopenRouterApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
zettel-ocr. Uses telegramTrigger, telegram, googleGemini, agent. Event-driven trigger; 16 nodes.
Source: https://github.com/SDG-027/11_Gen_AI_Integration/blob/81db6bd36507652319d91146c89003f86002b4af/06-n8n/zettel-ocr.json — 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.
Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v4). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v3). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
This workflow transforms your Telegram bot into an intelligent creative assistant. It can chat conversationally, fetch trending image prompts from PromptHero for inspiration, or perform a deep "remix"