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": "Reddit \u2014 India + student career subs (RSS aggregator)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 12
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
0,
0
],
"id": "a0red005-0000-4000-8000-000000000001",
"name": "Schedule Trigger"
},
{
"parameters": {
"jsCode": "// === Reddit subreddit aggregator ===\n// Fetches Atom feed (.rss endpoint returns Atom XML) for each subreddit\n// and flattens posts into a single item list. Reddit gates by User-Agent;\n// without a real-looking UA it returns 429s.\n//\n// Heavy keyword filter is applied later via the IF node; community posts\n// are noisy.\n\nconst SUBREDDITS = [\n { slug: 'developersIndia', displayName: 'r/developersIndia' },\n { slug: 'cscareerquestionsIndia', displayName: 'r/cscareerquestionsIndia' },\n { slug: 'csMajors', displayName: 'r/csMajors' }\n];\n\n// Per-subreddit cap so one busy sub doesn't dominate the run.\nconst PER_SUB_LIMIT = 10;\n\nfunction unescapeEntities(s) {\n if (!s) return '';\n return String(s)\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/&/g, '&')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ');\n}\n\nfunction stripHtml(s) {\n if (!s) return '';\n return unescapeEntities(String(s))\n .replace(/<[^>]*>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n// Parse Atom <entry> blocks via regex \u2014 Reddit's feeds are simple.\nfunction parseAtomEntries(xml) {\n const entries = [];\n const entryRe = /<entry\\b[\\s\\S]*?<\\/entry>/g;\n let match;\n while ((match = entryRe.exec(xml)) !== null) {\n const block = match[0];\n const title = (block.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/) || [])[1] || '';\n const link = (block.match(/<link[^>]*href=\"([^\"]+)\"/) || [])[1] || '';\n const content = (block.match(/<content[^>]*>([\\s\\S]*?)<\\/content>/) || [])[1] || '';\n const updated = (block.match(/<updated>([^<]+)<\\/updated>/) || [])[1] || '';\n entries.push({\n title: unescapeEntities(title.trim()),\n link,\n content: unescapeEntities(content),\n updated,\n });\n }\n return entries;\n}\n\n// Negative-keyword pre-filter: skip obvious meta/discussion posts before we\n// burn AI tokens trying to extract a non-existent opportunity. Tuned from\n// failures observed in production (megathreads, weekly threads, rants).\nconst SKIP_PHRASES = [\n 'megathread',\n 'monthly thread',\n 'weekly thread',\n 'who\\'s looking',\n 'who is looking',\n 'who\\'s hiring? -',\n 'who is hiring? -',\n 'discussion thread',\n 'general discussion',\n 'rant',\n 'meme',\n 'shitpost',\n 'salary thread',\n 'compensation thread',\n 'interview experience',\n 'career advice',\n 'doubt',\n 'help needed',\n];\n\nfunction looksLikeMeta(title) {\n const t = String(title || '').toLowerCase();\n if (!t) return true;\n return SKIP_PHRASES.some((p) => t.includes(p));\n}\n\nconst out = [];\nconst errors = [];\n\nfor (const sub of SUBREDDITS) {\n try {\n const xml = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://www.reddit.com/r/${sub.slug}/.rss`,\n headers: {\n Accept: 'application/atom+xml, application/xml',\n 'User-Agent': 'opportunity-os/1.0 (RSS aggregator; https://opportunity-os-eight.vercel.app)',\n },\n });\n const entries = parseAtomEntries(String(xml)).slice(0, PER_SUB_LIMIT);\n for (const e of entries) {\n if (!e.link) continue;\n if (looksLikeMeta(e.title)) continue; // drop megathreads, rants, etc.\n out.push({\n json: {\n title: e.title,\n link: e.link,\n contentSnippet: stripHtml(e.content).slice(0, 1000),\n content: e.content,\n subreddit: sub.displayName,\n source_name: `Reddit: ${sub.displayName}`,\n updated_at: e.updated,\n }\n });\n }\n } catch (e) {\n errors.push(`${sub.slug}: ${(e && e.message) || e}`);\n }\n}\n\nif (out.length === 0) {\n throw new Error('Reddit aggregator returned 0 usable posts. Errors: ' + (errors.join(' | ') || 'none'));\n}\nreturn out;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
208,
0
],
"id": "a0red005-0000-4000-8000-000000000002",
"name": "Fetch Reddit Subreddits"
},
{
"parameters": {
"maxItems": 15
},
"type": "n8n-nodes-base.limit",
"typeVersion": 1,
"position": [
416,
0
],
"id": "a0red005-0000-4000-8000-000000000003",
"name": "Limit"
},
{
"parameters": {
"options": {
"reset": false
}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
624,
0
],
"id": "a0red005-0000-4000-8000-000000000004",
"name": "Loop Over Items"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "k1",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "hiring",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k2",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "intern",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k3",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "fellowship",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k4",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "scholarship",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k5",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "hackathon",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k6",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "openings",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k7",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "freshers",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "k8",
"leftValue": "={{ ($json.title + ' ' + ($json.contentSnippet || '')).toLowerCase() }}",
"rightValue": "referral",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
832,
0
],
"id": "a0red005-0000-4000-8000-000000000005",
"name": "Filter Keywords"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/ingest/check-exists",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Ingest-Secret",
"value": "REPLACE_WITH_YOUR_INGEST_SHARED_SECRET"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "source_url",
"value": "={{ $json.link }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1040,
0
],
"id": "a0red005-0000-4000-8000-000000000006",
"name": "Check Exists"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "n1",
"leftValue": "={{ $json.exists }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1248,
0
],
"id": "a0red005-0000-4000-8000-000000000007",
"name": "If New"
},
{
"parameters": {
"amount": 7
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
1456,
-64
],
"id": "a0red005-0000-4000-8000-000000000008",
"name": "Wait"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/ai/extract",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Ingest-Secret",
"value": "REPLACE_WITH_YOUR_INGEST_SHARED_SECRET"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "text",
"value": "={{ ($('Loop Over Items').item.json.title + '\\n\\n' + ($('Loop Over Items').item.json.contentSnippet || '')).slice(0, 4000) }}"
},
{
"name": "source_url",
"value": "={{ $('Loop Over Items').item.json.link }}"
},
{
"name": "source_name",
"value": "={{ $('Loop Over Items').item.json.source_name }}"
},
{
"name": "hint",
"value": "={{ 'Reddit post from ' + $('Loop Over Items').item.json.subreddit + ' \u2014 community-shared opportunity. May be a referral, internship, or hiring announcement; expect informal phrasing.' }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"timeout": 60000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1648,
-64
],
"id": "a0red005-0000-4000-8000-000000000009",
"name": "AI Extract",
"retryOnFail": true,
"waitBetweenTries": 1000
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/ingest/upsert",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Ingest-Secret",
"value": "REPLACE_WITH_YOUR_INGEST_SHARED_SECRET"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "opportunity",
"value": "={{ $json.opportunity }}"
},
{
"name": "source_url",
"value": "={{ $('Loop Over Items').item.json.link }}"
},
{
"name": "source_name",
"value": "={{ $('Loop Over Items').item.json.source_name }}"
}
]
},
"options": {
"timeout": 15000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1888,
-64
],
"id": "a0red005-0000-4000-8000-000000000010",
"name": "Upsert Opportunity"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/log",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Ingest-Secret",
"value": "REPLACE_WITH_YOUR_INGEST_SHARED_SECRET"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "status",
"value": "skipped_filtered"
},
{
"name": "source_url",
"value": "={{ $json.link }}"
},
{
"name": "source_name",
"value": "={{ $json.source_name }}"
},
{
"name": "reason",
"value": "no opportunity-related keyword in title/content"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1136,
272
],
"id": "a0red005-0000-4000-8000-000000000011",
"name": "Log Filtered"
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:3000/api/log",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Ingest-Secret",
"value": "REPLACE_WITH_YOUR_INGEST_SHARED_SECRET"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "status",
"value": "skipped_duplicate"
},
{
"name": "source_url",
"value": "={{ $('Loop Over Items').item.json.link }}"
},
{
"name": "source_name",
"value": "={{ $('Loop Over Items').item.json.source_name }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1520,
272
],
"id": "a0red005-0000-4000-8000-000000000012",
"name": "Log Duplicate"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Fetch Reddit Subreddits",
"type": "main",
"index": 0
}
]
]
},
"Fetch Reddit Subreddits": {
"main": [
[
{
"node": "Limit",
"type": "main",
"index": 0
}
]
]
},
"Limit": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Filter Keywords",
"type": "main",
"index": 0
}
]
]
},
"Filter Keywords": {
"main": [
[
{
"node": "Check Exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Filtered",
"type": "main",
"index": 0
}
]
]
},
"Check Exists": {
"main": [
[
{
"node": "If New",
"type": "main",
"index": 0
}
]
]
},
"If New": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Duplicate",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "AI Extract",
"type": "main",
"index": 0
}
]
]
},
"AI Extract": {
"main": [
[
{
"node": "Upsert Opportunity",
"type": "main",
"index": 0
}
]
]
},
"Upsert Opportunity": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Log Filtered": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Log Duplicate": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Reddit — India + student career subs (RSS aggregator). Uses httpRequest. Scheduled trigger; 12 nodes.
Source: https://github.com/krishnagahlod/opportunity-os/blob/399dbb38874ec13c22922b64013cbc57a2845dc0/n8n-workflows/10-rss-reddit-india.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.
This template is for advanced users, content teams, and data analysts who need a robust, automated system for capturing YouTube transcripts. It’s ideal for those who monitor multiple channels and want
You are in the bad habit of always checking your feed to see if there are new videos? This workflow will help you get rid of this habit by delivering an email notification for each new video posted fr
This workflow demonstrates how to combine trend harvesting, channel intelligence, and AI scoring to select the best daily content ideas for short-form videos (YouTube Shorts / TikTok).
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
Multi Platform Content Generator from YouTube using AI & RSS. Uses httpRequest, googleSheets, rssFeedRead, lmChatOpenRouter. Scheduled trigger; 37 nodes.