This workflow corresponds to n8n.io template #10216 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request 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": "YnD0kvM9AYMjYxmj",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Multi-Site Product Price Monitor: Claude-Sonnet , Google Sheets & Telegram Alert",
"tags": [
{
"id": "7zsdOA50QGm7RNqx",
"name": "Monitoring",
"createdAt": "2025-10-23T16:41:17.031Z",
"updatedAt": "2025-10-23T16:41:17.031Z"
},
{
"id": "BL8TsHYj5FkNYzfi",
"name": "E-commerce",
"createdAt": "2025-10-23T16:41:16.985Z",
"updatedAt": "2025-10-23T16:41:16.985Z"
},
{
"id": "dlf9zFSN3j6s2jgO",
"name": "Business Intelligence",
"createdAt": "2025-10-23T16:41:17.008Z",
"updatedAt": "2025-10-23T16:41:17.008Z"
},
{
"id": "lpozR2Ct8reF9bCk",
"name": "AI",
"createdAt": "2025-10-23T16:41:17.062Z",
"updatedAt": "2025-10-23T16:41:17.062Z"
}
],
"nodes": [
{
"id": "306294b3-27b8-4821-9b04-0e8d210af07e",
"name": "\u23f0 Monitor Every 6 Hours",
"type": "n8n-nodes-base.scheduleTrigger",
"notes": "Triggers the workflow every 6 hours to check competitor data",
"position": [
-832,
352
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 6
}
]
}
},
"typeVersion": 1.2
},
{
"id": "8b934690-1e26-4810-820d-8ff785691adf",
"name": "Scrape a url and get its content",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1408,
96
],
"parameters": {
"url": "https://www.on.com/en-sg/shop/mens/shoes",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "90ed5eb7-6236-47e1-88c6-c5278efe24fa",
"name": "Message a model",
"type": "n8n-nodes-base.perplexity",
"position": [
-1568,
0
],
"parameters": {
"model": "sonar",
"options": {},
"messages": {
"message": [
{
"content": "Price of the shoe "
}
]
},
"requestOptions": {}
},
"credentials": {
"perplexityApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e1e006de-d363-4e5a-adbd-77ffc901f42d",
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
-1296,
0
],
"parameters": {
"options": {},
"fieldToSplitOut": "citations"
},
"typeVersion": 1
},
{
"id": "11fdf8bc-aa37-4f42-8bbe-8e16c47c8b47",
"name": "Get Results Apify",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1216,
272
],
"parameters": {
"url": "https://api.apify.com/v2/acts/compass~google-maps-extractor/runs/last/dataset/items?token=YOUR_TOKEN_HERE API KEY",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "b6a20e25-d82a-493b-b305-066e043f6ba1",
"name": "Start Actor Apify",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1616,
272
],
"parameters": {
"url": "https://api.apify.com/v2/acts/compass~google-maps-extractor/runs?token=YOUR_TOKEN_HERE API KEY",
"method": "POST",
"options": {},
"jsonBody": "{\n \"language\": \"en\",\n \"locationQuery\": \"worldwide\",\n \"maxCrawledPlacesPerSearch\": 50,\n \"searchStringsArray\": [\n \"shoe stores\",\n \"shoe shops\"\n ],\n \"skipClosedPlaces\": false\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "47a1e71a-6bbf-4cfe-82f1-b46744d247ee",
"name": "Converts unstructured AI text into organized, usable data fields",
"type": "n8n-nodes-base.code",
"notes": "Parses and validates the AI extracted data",
"position": [
-576,
192
],
"parameters": {
"jsCode": "// Parse AI response and clean data\nconst items = [];\n\nfor (const item of $input.all()) {\n try {\n // Parse the AI response\n let parsed;\n const response = item.json.choices?.[0]?.message?.content || item.json.message || '';\n \n // Remove markdown code blocks if present\n const cleaned = response.replace(/```json\\n?|```\\n?/g, '').trim();\n \n try {\n parsed = JSON.parse(cleaned);\n } catch (e) {\n // Try to extract JSON from the response\n const jsonMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n parsed = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error('Could not parse JSON from AI response');\n }\n }\n \n // Enrich with metadata\n items.push({\n json: {\n ...parsed,\n scrapedAt: new Date().toISOString(),\n priceChange: 0, // Will be calculated in comparison\n priceChangePercent: 0,\n isNewLow: false,\n alertLevel: 'none'\n }\n });\n } catch (error) {\n console.error('Failed to parse item:', error.message);\n // Add error item for debugging\n items.push({\n json: {\n error: error.message,\n rawResponse: item.json,\n competitorName: 'Parse Error',\n scrapedAt: new Date().toISOString()\n }\n });\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "3e8d7348-ccef-4cb5-aca4-d5ed6f210199",
"name": "\ud83d\udd00 Merge Current with Historical1",
"type": "n8n-nodes-base.merge",
"notes": "Combines current scrape with historical data for comparison",
"position": [
-384,
224
],
"parameters": {
"mode": "combine",
"options": {},
"fieldsToMatchString": "rawResponse.message.content"
},
"typeVersion": 3
},
{
"id": "7e24cbab-31e4-47ba-97f2-9a7a71335905",
"name": "\ud83d\udd0d Detect Price & Stock Changes1",
"type": "n8n-nodes-base.code",
"notes": "Intelligent change detection with alert level classification",
"position": [
-208,
224
],
"parameters": {
"jsCode": "// Compare current prices with historical and detect changes\nconst results = [];\n\nfor (const item of $input.all()) {\n const current = item.json;\n \n // Skip error items\n if (current.error) {\n results.push({ json: current });\n continue;\n }\n \n // Find historical data for this competitor\n const historical = $('\ud83d\udcca Read Historical Data').all()\n .find(h => h.json.competitorName === current.competitorName);\n \n let alertLevel = 'none';\n let changes = [];\n \n if (historical && historical.json.currentPrice) {\n const oldPrice = parseFloat(historical.json.currentPrice);\n const newPrice = parseFloat(current.currentPrice);\n const priceChange = newPrice - oldPrice;\n const priceChangePercent = ((priceChange / oldPrice) * 100).toFixed(2);\n \n current.priceChange = priceChange;\n current.priceChangePercent = parseFloat(priceChangePercent);\n current.previousPrice = oldPrice;\n \n // Determine alert level based on price changes\n if (Math.abs(priceChangePercent) >= 20) {\n alertLevel = 'critical';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 10) {\n alertLevel = 'warning';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 5) {\n alertLevel = 'info';\n changes.push(`Minor price change: ${priceChangePercent}%`);\n }\n \n // Check if it's a new low price\n const historicalLow = parseFloat(historical.json.lowestPrice || oldPrice);\n if (newPrice < historicalLow) {\n current.isNewLow = true;\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('\ud83c\udfaf NEW LOWEST PRICE!');\n }\n current.lowestPrice = Math.min(newPrice, historicalLow);\n \n // Stock level changes\n if (historical.json.stockLevel !== current.stockLevel) {\n if (current.stockLevel === 'Out of Stock') {\n alertLevel = 'critical';\n changes.push('\ud83d\udce6 Product went OUT OF STOCK');\n } else if (current.stockLevel === 'Low Stock') {\n alertLevel = alertLevel === 'none' ? 'warning' : alertLevel;\n changes.push('\u26a0\ufe0f Stock level is LOW');\n } else if (historical.json.stockLevel === 'Out of Stock' && current.inStock) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('\u2705 Back in stock!');\n }\n }\n \n // Rating changes\n const oldRating = parseFloat(historical.json.rating || 0);\n const newRating = parseFloat(current.rating || 0);\n const ratingChange = newRating - oldRating;\n \n if (Math.abs(ratingChange) >= 0.5) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push(`\u2b50 Rating ${ratingChange > 0 ? 'improved' : 'dropped'} by ${Math.abs(ratingChange).toFixed(1)} stars`);\n }\n \n // Review count changes\n const oldReviews = parseInt(historical.json.reviewCount || 0);\n const newReviews = parseInt(current.reviewCount || 0);\n const reviewDiff = newReviews - oldReviews;\n \n if (reviewDiff > 0) {\n changes.push(`\ud83d\udcac ${reviewDiff} new review${reviewDiff > 1 ? 's' : ''}`);\n }\n } else {\n // First time seeing this competitor\n alertLevel = 'info';\n changes.push('\ud83c\udd95 First time tracking this competitor');\n current.lowestPrice = current.currentPrice;\n }\n \n current.alertLevel = alertLevel;\n current.changesSummary = changes.join(' | ');\n current.hasChanges = alertLevel !== 'none';\n \n results.push({ json: current });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "4b8b4578-e6aa-461a-ae0a-7289bd0bf7e3",
"name": "\ud83d\udcbe Update Historical Data1",
"type": "n8n-nodes-base.googleSheets",
"notes": "Saves current data to historical tracking sheet",
"position": [
-32,
320
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"inStock": "={{ $json.inStock }}",
"currency": "={{ $json.currency }}",
"scrapedAt": "={{ $json.scrapedAt }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"lowestPrice": "={{ $json.lowestPrice }}",
"productName": "={{ $json.productName }}",
"reviewCount": "={{ $json.reviewCount }}",
"currentPrice": "={{ $json.currentPrice }}",
"originalPrice": "={{ $json.originalPrice }}",
"competitorName": "={{ $json.competitorName }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "= {{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "28d2de55-ef62-40cd-a22b-a13eee3c2dc8",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1760,
144
],
"parameters": {},
"typeVersion": 1
},
{
"id": "62cf73d2-a328-463b-9ea5-0d3fa8e3ba56",
"name": "\ud83e\udd16 AI Extract Product Data using Claude-Sonnet 4.5",
"type": "@n8n/n8n-nodes-langchain.anthropic",
"position": [
-864,
192
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "claude-sonnet-4-5-20250929",
"cachedResultName": "claude-sonnet-4-5-20250929"
},
"options": {},
"messages": {
"values": [
{
"content": "You are an accurate e-commerce data parser. From the provided HTML, extract shoe details and output a pure JSON object\u2014no markdown or extra text.\n\nInclude the following fields:\n\n- productName: string \n- currentPrice: number (numeric value only, no currency symbols) \n- originalPrice: number (if discounted, otherwise same as currentPrice) \n- currency: string (USD, EUR, etc.) \n- inStock: boolean \n- stockLevel: string (\"In Stock\", \"Low Stock\", \"Out of Stock\", \"Limited\", etc.) \n- rating: number (0-5 scale) \n- reviewCount: number \n- lastUpdated: string (current ISO timestamp) \n- productUrl: string (from context) \n- competitorName: string (from context)\n\nOutput only the JSON object\u2014no notes, comments, or formatting.\n"
}
]
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e5998861-2d29-4fdb-a5c0-445cf79ac50e",
"name": "\ud83d\udd00 Merge Current with Historical",
"type": "n8n-nodes-base.merge",
"notes": "Combines current scrape with historical data for comparison",
"position": [
-1024,
176
],
"parameters": {
"numberInputs": 3
},
"typeVersion": 3
},
{
"id": "6815d6c1-43c5-49a8-8f41-eafa0ee83363",
"name": "\ud83d\udcca Read Historical Data",
"type": "n8n-nodes-base.googleSheets",
"notes": "Loads previous scan data for comparison",
"position": [
-624,
352
],
"parameters": {
"options": {},
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "0c300e39-74da-4947-ab75-1360158f48a1",
"name": "\ud83d\udcdd Log Alert Details",
"type": "n8n-nodes-base.googleSheets",
"notes": "Logs all alerts to separate tracking sheet",
"position": [
-48,
160
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"timestamp": "={{ $json.scrapedAt }}",
"alertLevel": "={{ $json.alertLevel }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"priceChange": "={{ $json.priceChange || 0 }}",
"productName": "={{ $json.productName }}",
"currentPrice": "={{ $json.currentPrice }}",
"previousPrice": "={{ $json.previousPrice || 'N/A' }}",
"changesSummary": "={{ $json.changesSummary }}",
"competitorName": "={{ $json.competitorName }}",
"priceChangePercent": "={{ $json.priceChangePercent || 0 }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Alert Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "1833ee8f-13ca-40f3-a3ce-e7c09bbff5fc",
"name": "Send a text message",
"type": "n8n-nodes-base.telegram",
"position": [
112,
240
],
"parameters": {
"text": "The competitors\u2019 pricing has been finalized.",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "1209134e-cb58-4525-bbcb-ffee2a890ddb",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2336,
-336
],
"parameters": {
"width": 2576,
"height": 880,
"content": "## Introduction\nAutomate price monitoring for e-commerce competitors\u2014ideal for retailers, analysts, and pricing teams. Scrapes competitor sites, \nextracts pricing/stock data via AI, detects changes, and sends instant alerts for dynamic pricing strategies.\n## How It Works\nScrapes competitor URLs via Firecrawl and Apify, extracts data with AI, detects price/stock changes, logs to Google Sheets, and sends Telegram alerts.\n## Workflow Template\nTrigger \u2192 Scrape URL \u2192 AI Extract \u2192 Parse \u2192 Merge Historical \u2192 Detect Changes \u2192 Update Sheets + Send Telegram Alert\n## Workflow Steps\n1. **Trigger & Scrape** \u2192 Manual/scheduled trigger \u2192 Firecrawl + Apify fetch competitor data\n2. **AI Processing** \u2192 Claude extracts product details \u2192 Parses and structures data\n3. **Change Detection** \u2192 Reads historical prices \u2192 Merges with current data \u2192 Identifies updates\n4. **Output** \u2192 Logs alerts to Sheets \u2192 Updates historical data \u2192 Sends Telegram notification\n## Setup Instructions\n**1. Firecrawl API**\nGet key from dashboard \u2192 Add to n8n\n**2. Apify API**\nGet key from console \u2192 Add to n8n \u2192 Configure actors\n**3. AI Model (Claude/OpenAI)**\nGet API key \u2192 Add to n8n\n**4. Google Sheets OAuth2**\nCreate OAuth2 in Google Cloud Console \u2192 Authorize in n8n \u2192 Enable API\n**5. Telegram Bot**\nCreate via BotFather \u2192 Get token & chat ID \u2192 Add to n8n\n**6. Spreadsheet Setup**\nCreate Sheet with required columns \u2192 Copy ID \u2192 Paste in workflow\n## Prerequisites\nSelf-hosted n8n, Firecrawl account, Apify account, Claude/OpenAI API key,\nGoogle account (Sheets OAuth2),Telegram bot\n## Customization\nAdd more URLs, adjust scraping intervals, change detection thresholds, switch to Slack/email alerts,\nintegrate databases\n## Benefits\nSaves 2+ hours daily, real-time tracking, automated alerts, historical analysis, multi-source scraping\n\n"
},
"typeVersion": 1
},
{
"id": "864886e9-21d0-402f-8f1f-46216a5e1373",
"name": "Pause the workflow to let the Apify scraping task finish",
"type": "n8n-nodes-base.wait",
"position": [
-1424,
272
],
"parameters": {
"amount": 25
},
"typeVersion": 1.1
},
{
"id": "5b798a21-b3e1-4fb4-816a-d32a63367933",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-304
],
"parameters": {
"color": 5,
"width": 352,
"height": 256,
"content": "## Google Sheets Structure\n**Required Columns:**\n- **Product Name** (Column A)\n- **Current Price** (Column B)\n- **Previous Price** (Column C)\n- **Stock Status** (Column D)\n- **Last Updated** (Column E)\n- **URL** (Column F)\n- **Change Detected** (Column G)"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "7e9182a3-7a64-45a2-9632-3de5d2917fcd",
"connections": {
"Split Out": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Current with Historical",
"type": "main",
"index": 0
}
]
]
},
"Message a model": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Get Results Apify": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Current with Historical",
"type": "main",
"index": 2
}
]
]
},
"Start Actor Apify": {
"main": [
[
{
"node": "Pause the workflow to let the Apify scraping task finish",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcdd Log Alert Details": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"\u23f0 Monitor Every 6 Hours": {
"main": [
[
{
"node": "\ud83d\udcca Read Historical Data",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Read Historical Data": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Current with Historical1",
"type": "main",
"index": 1
}
]
]
},
"\ud83d\udcbe Update Historical Data1": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Scrape a url and get its content": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Current with Historical",
"type": "main",
"index": 1
}
]
]
},
"\ud83d\udd00 Merge Current with Historical": {
"main": [
[
{
"node": "\ud83e\udd16 AI Extract Product Data using Claude-Sonnet 4.5",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd0d Detect Price & Stock Changes1": {
"main": [
[
{
"node": "\ud83d\udcbe Update Historical Data1",
"type": "main",
"index": 0
},
{
"node": "\ud83d\udcdd Log Alert Details",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd00 Merge Current with Historical1": {
"main": [
[
{
"node": "\ud83d\udd0d Detect Price & Stock Changes1",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
},
{
"node": "Scrape a url and get its content",
"type": "main",
"index": 0
},
{
"node": "Start Actor Apify",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udd16 AI Extract Product Data using Claude-Sonnet 4.5": {
"main": [
[
{
"node": "Converts unstructured AI text into organized, usable data fields",
"type": "main",
"index": 0
}
]
]
},
"Pause the workflow to let the Apify scraping task finish": {
"main": [
[
{
"node": "Get Results Apify",
"type": "main",
"index": 0
}
]
]
},
"Converts unstructured AI text into organized, usable data fields": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Current with Historical1",
"type": "main",
"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.
anthropicApifirecrawlApigoogleSheetsOAuth2ApihttpBearerAuthperplexityApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate price monitoring for e-commerce competitors—ideal for retailers, analysts, and pricing teams. Scrapes competitor sites, extracts pricing/stock data via AI, detects changes, and sends instant alerts for dynamic pricing strategies.
Source: https://n8n.io/workflows/10216/ — 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.
Turn any Amazon India product URL into a fully-edited 10-second lifestyle video and auto-publish it to Instagram, Facebook, X (Twitter), LinkedIn, YouTube, and Threads — with platform-optimized captio
Schedule Trigger runs every 6 hours (customizable) Apify Scraper fetches Upwork jobs matching your criteria Deduplication filters out jobs you've already seen AI Scoring (GPT-4) evaluates fit, client
This workflow runs on a weekly schedule, queries Cloudflare GraphQL Analytics for requests matching known AI crawler user-agents, aggregates request counts per bot, generates a short summary with Anth
Women creators, homemakers-turned-entrepreneurs, and feminine lifestyle brands who want a graceful, low-lift way to keep an eye on competitor content and spark weekly ideas.
This workflow runs daily to search flight prices via the Apify Google Flights actor, logs each check to Google Sheets, and sends a Telegram alert when the lowest price drops below your threshold and i