This workflow follows the Chainllm → 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": "q1p9YZ4v7KVtbzFE",
"name": "CreatorLens - WF03 Trend Agent",
"description": "Feature 2 - Keyword Trend Analyzer. Searches YouTube for niche videos from the last 14 days (max 3 searches), computes view velocity, Gemini extracts trending keywords with opportunity scores into keyword_trends.",
"settings": {
"executionOrder": "v1",
"availableInMCP": true,
"binaryMode": "separate"
},
"connections": {
"Trend Webhook": {
"main": [
[
{
"node": "Normalize Input",
"type": "main",
"index": 0
}
]
]
},
"Normalize Input": {
"main": [
[
{
"node": "Mark Run Running",
"type": "main",
"index": 0
}
]
]
},
"Mark Run Running": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Build Search Queries": {
"main": [
[
{
"node": "YT Niche Search",
"type": "main",
"index": 0
}
]
]
},
"YT Niche Search": {
"main": [
[
{
"node": "Dedupe Video IDs",
"type": "main",
"index": 0
}
]
]
},
"Dedupe Video IDs": {
"main": [
[
{
"node": "Found Videos?",
"type": "main",
"index": 0
}
]
]
},
"Found Videos?": {
"main": [
[
{
"node": "YT Trend Video Stats",
"type": "main",
"index": 0
}
],
[
{
"node": "Save Empty Trends",
"type": "main",
"index": 0
}
]
]
},
"YT Trend Video Stats": {
"main": [
[
{
"node": "Compute Velocity",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark Run Failed",
"type": "main",
"index": 0
}
]
]
},
"Compute Velocity": {
"main": [
[
{
"node": "Gemini Trend Analysis",
"type": "main",
"index": 0
}
]
]
},
"Gemini Trend Analysis": {
"main": [
[
{
"node": "Parse Gemini Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark Run Failed",
"type": "main",
"index": 0
}
]
]
},
"Gemini Flash Lite": {
"ai_languageModel": [
[
{
"node": "Gemini Trend Analysis",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Parse Gemini Response": {
"main": [
[
{
"node": "Save Keyword Trends",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark Run Failed",
"type": "main",
"index": 0
}
]
]
},
"Save Keyword Trends": {
"main": [
[
{
"node": "Mark Run Succeeded",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark Run Failed",
"type": "main",
"index": 0
}
]
]
},
"Save Empty Trends": {
"main": [
[
{
"node": "Mark Run Succeeded",
"type": "main",
"index": 0
}
]
]
}
},
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "trend-agent",
"authentication": "headerAuth",
"options": {}
},
"id": "4a13aacf-7794-414b-aaed-f54388053d99",
"name": "Trend Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
304
]
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "={{ { \"runId\": $json.body?.runId ?? $json.runId, \"channelId\": $json.body?.channelId ?? $json.channelId, \"niche\": $json.body?.niche ?? $json.niche } }}",
"options": {}
},
"id": "c38dd2b4-66f4-4a3f-8ede-fb561206f335",
"name": "Normalize Input",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
224,
304
]
},
{
"parameters": {
"method": "PATCH",
"url": "=https://ukaxegkcrgsxeizskrot.supabase.co/rest/v1/agent_runs?id=eq.{{ $json.runId }}",
"authentication": "genericCredentialType",
"nodeCredentialType": "supabaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Prefer",
"value": "return=representation"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"status\": \"running\" } }}",
"options": {},
"genericAuthType": "httpCustomAuth"
},
"id": "8f3d6dd8-510b-46fd-b1da-5a97506cb9b0",
"name": "Mark Run Running",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
448,
304
]
},
{
"parameters": {
"jsCode": "const niche = ($('Normalize Input').first().json.niche || '').trim();\nif (!niche) throw new Error('No niche provided');\nconst year = new Date().getFullYear();\nconst queries = [niche, `${niche} ${year}`, `best ${niche}`].slice(0, 3);\nconst publishedAfter = new Date(Date.now() - 14 * 86400000).toISOString();\nreturn queries.map(q => ({ json: { query: q, publishedAfter } }));"
},
"id": "c2b3d18c-9860-497e-b309-9cef909c77db",
"name": "Build Search Queries",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
304
]
},
{
"parameters": {
"url": "=https://www.googleapis.com/youtube/v3/search?part=snippet&q={{ encodeURIComponent($json.query) }}&type=video&order=viewCount&publishedAfter={{ $json.publishedAfter }}&maxResults=25",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth",
"options": {}
},
"id": "0d47c6a0-508b-40ac-9dea-cd1bcc896097",
"name": "YT Niche Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
880,
304
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const ids = new Set();\nfor (const it of $input.all()) {\n for (const v of (it.json.items || [])) {\n if (v.id && v.id.videoId) ids.add(v.id.videoId);\n }\n}\nreturn [{ json: { idCsv: Array.from(ids).slice(0, 50).join(',') } }];"
},
"id": "f61fa03f-2295-4ed9-8081-189b28e160bd",
"name": "Dedupe Video IDs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1104,
304
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.idCsv }}",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"rightValue": ""
}
],
"combinator": "and"
},
"options": {}
},
"id": "672dc228-855b-41dd-8ef3-692b9a21546c",
"name": "Found Videos?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1328,
304
]
},
{
"parameters": {
"url": "=https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&id={{ $json.idCsv }}&maxResults=50",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth",
"options": {}
},
"id": "587be416-59cd-4f3c-a271-d17d6c64723c",
"name": "YT Trend Video Stats",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1552,
208
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const items = $input.first().json.items || [];\nconst now = Date.now();\nconst videos = items.map(v => {\n const views = Number((v.statistics && v.statistics.viewCount) || 0);\n const days = Math.max(1, (now - new Date(v.snippet.publishedAt).getTime()) / 86400000);\n return {\n title: v.snippet.title,\n channel: v.snippet.channelTitle,\n published_at: v.snippet.publishedAt,\n views,\n views_per_day: Math.round(views / days),\n likes: Number((v.statistics && v.statistics.likeCount) || 0),\n comments: Number((v.statistics && v.statistics.commentCount) || 0),\n tags: (v.snippet.tags || []).slice(0, 8)\n };\n}).sort((a, b) => b.views_per_day - a.views_per_day).slice(0, 40);\nconst niche = $('Normalize Input').first().json.niche;\nconst prompt = `You are a YouTube trend analyst. Niche: \"${niche}\".\\nBelow are recent (last 14 days) high-velocity videos in this niche, sorted by views per day.\\n\\nIdentify trending keywords/topics a creator in this niche should make videos about NOW.\\n\\nReturn ONLY valid JSON (no markdown fences):\\n{\"keywords\":[{\"keyword\":\"...\",\"momentum\":\"exploding|rising|steady\",\"avg_views\":<number>,\"competition\":\"low|medium|high\",\"opportunity_score\":<0-100>,\"sample_titles\":[\"up to 3 real titles from the data\"]}],\"insights\":\"3-4 sentences on overall niche dynamics\"}\\n\\nReturn 6-10 keywords ordered by opportunity_score descending.\\n\\nVideos:\\n${JSON.stringify(videos)}`;\nreturn [{ json: { prompt, video_count: videos.length } }];"
},
"id": "9651df41-7057-4706-be06-9ea7f5c1d432",
"name": "Compute Velocity",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1760,
208
]
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}",
"messages": {
"messageValues": [
{
"message": "You are a YouTube niche trend analyst. You always respond with raw JSON only - no markdown fences, no extra text."
}
]
},
"batching": {}
},
"id": "d6ee4627-2939-472c-889a-59a81e226006",
"name": "Gemini Trend Analysis",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.9,
"position": [
1904,
80
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"modelName": "models/gemini-3.1-flash-lite",
"options": {
"maxOutputTokens": 4096,
"temperature": 0.4
}
},
"id": "1807161d-8431-4a3e-a70b-cf97f7005547",
"name": "Gemini Flash Lite",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1.1,
"position": [
1952,
432
]
},
{
"parameters": {
"jsCode": "const raw = $input.first().json;\nlet text = raw.text ?? raw.output ?? raw.response ?? '';\nif (typeof text === 'object') text = JSON.stringify(text);\ntext = String(text).trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\nlet parsed;\ntry { parsed = JSON.parse(text); } catch (e) { throw new Error('Gemini returned unparseable JSON: ' + text.slice(0, 300)); }\nif (parsed.output && !parsed.keywords) parsed = parsed.output;\nif (!Array.isArray(parsed.keywords)) throw new Error('Model output missing keywords array');\nreturn [{ json: { keywords: parsed.keywords, insights: parsed.insights || '' } }];"
},
"id": "8b6be335-bfcd-4d99-b6a6-20ddc4e1c6bf",
"name": "Parse Gemini Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2208,
208
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"method": "POST",
"url": "https://ukaxegkcrgsxeizskrot.supabase.co/rest/v1/keyword_trends",
"authentication": "genericCredentialType",
"nodeCredentialType": "supabaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Prefer",
"value": "return=representation"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"channel_id\": $(\"Normalize Input\").item.json.channelId, \"niche\": $(\"Normalize Input\").item.json.niche, \"result\": { \"keywords\": $json.keywords, \"insights\": $json.insights, \"video_sample_size\": $(\"Compute Velocity\").item.json.video_count } } }}",
"options": {},
"genericAuthType": "httpCustomAuth"
},
"id": "0777097e-e828-4e3b-b8cf-81e187cb2741",
"name": "Save Keyword Trends",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
2432,
208
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"method": "PATCH",
"url": "=https://ukaxegkcrgsxeizskrot.supabase.co/rest/v1/agent_runs?id=eq.{{ $(\"Normalize Input\").item.json.runId }}",
"authentication": "genericCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"status\": \"succeeded\", \"finished_at\": $now.toISO() } }}",
"options": {},
"genericAuthType": "httpCustomAuth"
},
"id": "eb6bad49-1049-4b99-9076-dd7ae76ee95c",
"name": "Mark Run Succeeded",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
2640,
304
]
},
{
"parameters": {
"method": "PATCH",
"url": "=https://ukaxegkcrgsxeizskrot.supabase.co/rest/v1/agent_runs?id=eq.{{ $(\"Normalize Input\").item.json.runId }}",
"authentication": "genericCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"status\": \"failed\", \"finished_at\": $now.toISO(), \"error\": ($json.error?.message ?? $json.error ?? \"Trend agent failed\").toString().slice(0, 500) } }}",
"options": {},
"genericAuthType": "httpCustomAuth"
},
"id": "02c948bc-1ed4-455a-9e6d-a6a4bb4b7cdb",
"name": "Mark Run Failed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1408,
640
]
},
{
"parameters": {
"method": "POST",
"url": "https://ukaxegkcrgsxeizskrot.supabase.co/rest/v1/keyword_trends",
"authentication": "genericCredentialType",
"nodeCredentialType": "supabaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Prefer",
"value": "return=representation"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"channel_id\": $(\"Normalize Input\").item.json.channelId, \"niche\": $(\"Normalize Input\").item.json.niche, \"result\": { \"keywords\": [], \"insights\": \"No recent high-velocity videos were found for this niche in the last 14 days. Try a broader niche phrase.\", \"video_sample_size\": 0 } } }}",
"options": {},
"genericAuthType": "httpCustomAuth"
},
"id": "a467874c-04c0-4a61-9186-9cba453482ca",
"name": "Save Empty Trends",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1552,
448
]
},
{
"parameters": {
"content": "## WF03 Trend Agent\nsearch.list costs 100 quota units per call - capped at 3 queries per run (~300 units). Web app enforces max 1 run per niche per 6h.\n\n### Credentials to attach\n1. Webhook: **CreatorLens Webhook Secret**\n2. Supabase HTTP nodes: **Supabase CreatorLens**\n3. YT nodes: **YouTube API Key** (Query Auth, param `key`)\n4. Gemini Flash Lite: **Gemini**",
"height": 344,
"width": 520,
"color": 4
},
"id": "d032eaae-01ca-4d43-ab70-06f47e11f8db",
"name": "Sticky Note 59ff20d9",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-64,
-256
]
}
],
"meta": {
"aiBuilderAssisted": true,
"builderVariant": "mcp"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
CreatorLens - WF03 Trend Agent. Uses httpRequest, chainLlm, lmChatGoogleGemini. Webhook trigger; 17 nodes.
Source: https://github.com/Ahmi-786/AI-Driven-Content-Analytics/blob/97d966d365a4e5628206f47133567d08341291e3/n8n-workflows/wf03-trend-agent.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.
ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
my-secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 86 nodes.
secretaria. Uses postgres, n8n-nodes-evolution-api, openAi, httpRequest. Webhook trigger; 71 nodes.
Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.