This workflow corresponds to n8n.io template #11159 — we link there as the canonical source.
This workflow follows the Agent → Airtable 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 →
{
"id": "gqu5n9yXGwrXkwLD",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Ingredients Analyst",
"tags": [],
"nodes": [
{
"id": "fc0ac288-3105-47a1-aa92-d76440e02c93",
"name": "Data Aggregation",
"type": "n8n-nodes-base.code",
"position": [
672,
-80
],
"parameters": {
"jsCode": "// Get data from previous nodes\n// Note: Ensure your input nodes are named 'Watchlist' and 'Preferences'\nconst watchlistItems = $('Watchlist').all();\nconst prefItems = $('Preferences').all();\n\n// 1. Extract Persona & Language\n// Finds the row where Key is 'Persona' or 'Language'\nconst persona = prefItems.find(i => i.json.Key === 'Persona')?.json.Value || \"A helpful assistant\";\nconst language = prefItems.find(i => i.json.Key === 'Language')?.json.Value || \"English\";\n\n// 2. Sort Ingredients into Categories\n// We only want 'Active' items\nconst activeItems = watchlistItems.filter(i => i.json.Status === 'Active');\n\nconst badStuff = activeItems\n .filter(i => i.json.Type === 'Bad Stuff')\n .map(i => i.json['Ingredient Name'])\n .join(\", \");\n\nconst goodStuff = activeItems\n .filter(i => i.json.Type === 'Good Stuff')\n .map(i => i.json['Ingredient Name'])\n .join(\", \");\n\n// 3. Return Clean Data for the AI\nreturn {\n json: {\n persona: persona,\n target_language: language,\n bad_list: badStuff,\n good_list: goodStuff\n }\n};"
},
"typeVersion": 2
},
{
"id": "bee23e18-c3ae-4bd4-be5c-8b93d1b42817",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1472,
-80
],
"parameters": {
"text": "=You are a verbose {{ $('Data Aggregation').item.json.persona }}. Answer in {{ $('Data Aggregation').item.json.target_language }}.\n\n**YOUR TASK:**\nAnalyze the provided OCR text from a product label. \nCompare it strictly against the user's lists using FUZZY MATCHING (e.g. \"E120\" = \"Carmine\").\n\n**\ud83d\udd34 BAD LIST (The Veto):** {{ $('Data Aggregation').item.json.bad_list }}\n**\ud83d\udfe2 GOOD LIST (The Preference):** {{ $('Data Aggregation').item.json.good_list }}\n\n**\u26a0\ufe0f THE CONSTITUTION (Logic Rules):**\n1. **Bad Stuff overrides Good Stuff.** If an ingredient is on the Good List (e.g., \"Egg\") but the label contains a specific derivative on the Bad List (e.g., \"Albumin\"), the product is **UNSAFE**.\n2. **The \"Safe\" Verdict:** The product is ONLY \"safe\" if ZERO items from the Bad List are found.\n3. **Contradictions:** If an item appears to be in both categories due to fuzzy matching, treat it as **BAD**.\n\n**OCR TEXT TO ANALYZE:**\n\"\"\"\n{{ $json.output }}\n\"\"\"\n\n**OUTPUT FORMAT (HTML ONLY):**\nReturn ONLY a JSON object.\n{\n \"detected_bad\": [\"List exactly what was found\"],\n \"detected_good\": [\"List exactly what was found\"],\n \"is_safe\": true/false,\n \"summary_msg\": \"Your response in HTML. Use <b>bold</b> for emphasis. Start with \ud83d\udea8 foe unsafe and \u2705 for safe. If unsafe, explain WHICH ingredient triggered the veto. Use lots of emojies, making the summary understood fast.\"\n}\n",
"options": {},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "086c3b46-dcc5-48f3-be8a-ff555aa587ff",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1192,
144
],
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "benhaotang/Nanonets-OCR-s:F16"
},
"options": {
"responseFormat": "text"
},
"responsesApiEnabled": false
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "0dcc9287-6d9f-4db5-afdd-eb8cfaf39efd",
"name": "AI OCR Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1120,
-80
],
"parameters": {
"text": "Extract the full text as output from the document as if you were reading it.",
"options": {
"passthroughBinaryImages": true
},
"promptType": "define",
"needsFallback": true
},
"typeVersion": 3
},
{
"id": "303f78e7-1295-43ca-8c28-6f6e118412e3",
"name": "JSON Parse & Routing",
"type": "n8n-nodes-base.code",
"position": [
1824,
-80
],
"parameters": {
"jsCode": "// The AI Agent node places the final text in the 'output' field\nconst content = $input.item.json.output;\n\n// Logic to find the JSON object within the text (in case the AI adds chatter)\n// This regex looks for content between { and }\nconst jsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n\nif (jsonMatch) {\n // Parse the found JSON string\n const cleanJson = jsonMatch[0];\n return { json: JSON.parse(cleanJson) };\n} else {\n // Fallback if no JSON found (prevents workflow crash)\n return {\n json: {\n is_safe: false,\n summary_msg: \"\u26a0\ufe0f AI Error: Could not parse analysis results.\",\n detected_bad: [],\n detected_good: []\n }\n };\n}"
},
"typeVersion": 2
},
{
"id": "06fe722f-1a28-4f0f-bd28-35781ba23a0f",
"name": "Send a text message",
"type": "n8n-nodes-base.telegram",
"position": [
2048,
-80
],
"parameters": {
"text": "={{ $json.summary_msg }}\n\n\nDetected Bad Items: {{ $('JSON Parse & Routing').item.json.detected_bad.join(\", \") }}\n\nDetected Good Items: {{ $('JSON Parse & Routing').item.json.detected_good.join(\", \") }}",
"chatId": "={{ $('Telegram Trigger').last().json.message.chat.id }}",
"additionalFields": {
"parse_mode": "HTML",
"reply_to_message_id": "={{ $('Telegram Trigger').last().json.message.message_id }}"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "a3c9ec6b-5bb2-42f3-a96f-95f4835f399f",
"name": "Create a record",
"type": "n8n-nodes-base.airtable",
"position": [
2272,
-80
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appwuO8h6qLVULY20",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20",
"cachedResultName": "Ingredients Brain"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblxNCYzl618GOEeW",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20/tblxNCYzl618GOEeW",
"cachedResultName": "Logs"
},
"columns": {
"value": {
"Message ID": "={{ $json.result.message_id.toString() }}",
"AI Response": "={{ $('JSON Parse & Routing').item.json.summary_msg }}",
"Detected Stuff": "={{ $('JSON Parse & Routing').item.json.detected_bad.join(\", \") }}"
},
"schema": [
{
"id": "Message ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Message ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Detected Stuff",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Detected Stuff",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "AI Response",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "AI Response",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"typecast": true
},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "964083b7-e40b-4255-af7f-3c0474a34d44",
"name": "Get a file",
"type": "n8n-nodes-base.telegram",
"position": [
896,
-80
],
"parameters": {
"fileId": "={{ $('Telegram Trigger').last().json.message.photo[$('Telegram Trigger').last().json.message.photo.length - 1].file_id }}",
"resource": "file",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "12d12ac3-5d1c-4123-b677-a8b6dd55ec6b",
"name": "Watchlist",
"type": "n8n-nodes-base.airtable",
"position": [
240,
-80
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appwuO8h6qLVULY20",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20",
"cachedResultName": "Ingredients Brain"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblcSxoDacaEYM72z",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20/tblcSxoDacaEYM72z",
"cachedResultName": "Watchlist"
},
"options": {},
"operation": "search",
"filterByFormula": "Status = \"Active\""
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "54b17913-0126-48cb-9667-43fce3b35fb1",
"name": "Preferences",
"type": "n8n-nodes-base.airtable",
"position": [
448,
-80
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appwuO8h6qLVULY20",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20",
"cachedResultName": "Ingredients Brain"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblg4MpqDoqodiGO7",
"cachedResultUrl": "https://airtable.com/appwuO8h6qLVULY20/tblg4MpqDoqodiGO7",
"cachedResultName": "Preferences"
},
"options": {},
"operation": "search"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"typeVersion": 2.1
},
{
"id": "3d8804b2-8fb4-4306-a556-5c2304f6b1ed",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-16,
-80
],
"parameters": {
"updates": [
"message"
],
"additionalFields": {
"download": true
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "5354c9c0-b24b-4b63-8d59-7fa0f23ad727",
"name": "Done!",
"type": "n8n-nodes-base.noOp",
"position": [
2496,
-80
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d4c3a9f7-190a-4517-ae8c-9f552768662b",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1544,
144
],
"parameters": {
"options": {
"temperature": 0.2
},
"modelName": "models/gemini-2.0-flash-lite-001"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6f47bacb-c8bc-4d40-8316-24072891976c",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-896,
-464
],
"parameters": {
"width": 720,
"height": 848,
"content": "# \ud83d\udee1\ufe0f Personal Ingredient Bodyguard\n**Turn Telegram into a smart food safety scanner!**\n\nThis workflow takes a photo from Telegram, reads the text using Local OCR, and uses Gemini to cross-reference it against your personal **Good** & **Bad** ingredient lists.\n\n### \ud83d\ude80 **Quick Start Guide**\n\n**1. Get the Database **\nYou need the specific Airtable configuration for this to work.\n\ud83d\udc49 [**Click here to Copy the Base**](https://airtable.com/appwuO8h6qLVULY20/shrYVCqWgPtFDPz0q)\n*(Don't have an account? [Sign up for Airtable here](https://airtable.com/invite/r/Isr7G94S))*\n\n**2. Connect n8n to Airtable**\nNeed help with the API key?\n\ud83d\udcfa [**Watch this 5-min Setup Tutorial**](https://www.youtube.com/watch?v=xFFfkBeI2rQ)\n\n**3. Customize**\nGo to the **Preferences** table in your new base to change the AI's \"Persona\" (e.g., Sarcastic Bodyguard) and Language!\n\n---\n### \u26a0\ufe0f **IMPORTANT DISCLAIMER**\n> **This tool is for informational purposes only.**\n>\n> 1. **Not Medical Advice:** Do not rely on this for life-threatening allergies.\n> 2. **AI Limitations:** OCR can misread text, and AI can hallucinate.\n> 3. **Verify:** Always double-check the physical product label.\n>\n> *Use at your own risk.*"
},
"typeVersion": 1
},
{
"id": "2e13f201-9837-4b12-8355-4232bc6aeaf4",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
-608
],
"parameters": {
"color": 5,
"width": 336,
"height": 1184,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n# \ud83e\udd16 Telegram nodes: Input & Output\n**Connect your Telegram Bot**\n\n1. Talk to **@BotFather** on Telegram to create a new bot.\n2. Copy the **API Token**.\n3. Create a **Telegram Credential** in n8n and paste the token.\n\n**Nodes in this group:**\n* **Trigger:** Listens for incoming photos.\n* **Get File:** Downloads the highest resolution image for the OCR.\n* **Send Message:** Replies to the user with the result (HTML formatted)."
},
"typeVersion": 1
},
{
"id": "1efcac35-a3d8-4a78-a9a0-5eebb9fbe7f9",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1088,
-512
],
"parameters": {
"color": 6,
"width": 640,
"height": 992,
"content": "# \ud83d\udc41\ufe0f Vision & Logic\n**Hybrid AI Architecture**\n\nThis section splits the work to save costs and increase privacy.\n\n**1. The Eyes (OCR)** \ud83d\udc41\ufe0f\n* **Node:** `AI OCR Agent` + `OpenAI Chat Model`\n* **Config:** This is set up to use a **Local Model** (`benhaotang/Nanonets-OCR-s:F16`) via an OpenAI-compatible endpoint.\n* *Tip: If you don't host this model locally, swap the Chat Model node for a standard GPT-4o or Gemini Vision node.*\n\n**2. The Brain (Analyst)** \ud83e\udde0\n* **Node:** `AI Agent` + `Google Gemini`\n* **Config:** This uses **Gemini Flash/Pro** to analyze the text. It strictly follows the \"Veto Rules\" defined in your Airtable."
},
"typeVersion": 1
},
{
"id": "a73a681e-a824-4c00-a3ac-28d329d19996",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
224,
-608
],
"parameters": {
"color": 7,
"width": 1776,
"height": 1184,
"content": "# \u2699\ufe0f Data Processing\n\n* **Data Aggregation:** Pulls your `Active` watchlist and `Preferences` from Airtable and formats them into a prompt for the AI.\n* **JSON Parse:** Cleans the AI's output to ensure n8n can read the `Safe/Unsafe` verdict correctly.\n\n> **Logic Note:** The code enforces that **Bad Stuff** always overrides **Good Stuff**."
},
"typeVersion": 1
},
{
"id": "fbf54b05-040a-4166-a8d3-2fb7c7aca150",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2384,
-608
],
"parameters": {
"color": 4,
"width": 352,
"height": 1184,
"content": "# \u2705 Workflow Complete!\n**Cycle Finished Successfully**\n\nIf the execution reaches this node:\n1. \ud83d\udcf8 Image was processed.\n2. \ud83e\uddea Ingredients were analyzed.\n3. \ud83d\udcf2 User received the Telegram reply.\n4. \ud83d\udcdd Result was logged to Airtable.\n\n---\n**Ready for the next scan!**"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c784db38-81b1-4126-b8b8-6e73cb5b6a26",
"connections": {
"AI Agent": {
"main": [
[
{
"node": "JSON Parse & Routing",
"type": "main",
"index": 0
}
]
]
},
"Watchlist": {
"main": [
[
{
"node": "Preferences",
"type": "main",
"index": 0
}
]
]
},
"Get a file": {
"main": [
[
{
"node": "AI OCR Agent",
"type": "main",
"index": 0
}
]
]
},
"Preferences": {
"main": [
[
{
"node": "Data Aggregation",
"type": "main",
"index": 0
}
]
]
},
"AI OCR Agent": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Create a record": {
"main": [
[
{
"node": "Done!",
"type": "main",
"index": 0
}
]
]
},
"Data Aggregation": {
"main": [
[
{
"node": "Get a file",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Watchlist",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI OCR Agent",
"type": "ai_languageModel",
"index": 0
},
{
"node": "AI OCR Agent",
"type": "ai_languageModel",
"index": 1
}
]
]
},
"Send a text message": {
"main": [
[
{
"node": "Create a record",
"type": "main",
"index": 0
}
]
]
},
"JSON Parse & Routing": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"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.
airtableTokenApigooglePalmApiopenAiApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Turn your Telegram bot into an intelligent food safety scanner. This workflow analyzes photos of ingredient labels sent via Telegram, extracts the text using AI, and cross-references it against your personal database of "Good" and "Bad" ingredients in Airtable.
Source: https://n8n.io/workflows/11159/ — 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.
Transform your salon/service business with this streamlined Telegram automation system featuring Claude integration, zero-setup database management, and intelligent conversation handling. Claude MCP I
Unlock the Power of Language with Personalized AI Learning! MOTION TUTOR is a revolutionary AI-powered language learning platform that adapts to your progress and guides you from basic vocabulary to c
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.
Template Carnaval - time instagram. Uses toolWorkflow, lmChatOpenAi, memoryBufferWindow, agent. Event-driven trigger; 56 nodes.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.