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": "Market Radar - Weekly Intelligence",
"nodes": [
{
"id": "trigger-schedule",
"name": "Weekly Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
400
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 19 * * 6"
}
]
}
},
"notes": "Default: Saturday 19:00 UTC. Adjust to your own timezone and day - see n8n/README.md."
},
{
"id": "start-fanout",
"name": "Start Fan-Out",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
220,
400
],
"parameters": {}
},
{
"id": "src-newsroom",
"name": "Company Newsroom RSS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
-300
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "https://example.com/newsroom/rss",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"notes": "Replace with your own company's newsroom/investor RSS feed."
},
{
"id": "parse-newsroom",
"name": "Parse Newsroom",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
-300
],
"parameters": {
"jsCode": "const windowDays = 7;\nconst now = new Date();\nconst cutoff = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\nlet items = [];\ntry {\n const xml = $input.first().json.data || '';\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/gi;\n const titleRegex = /<title>(?:<!\\[CDATA\\[)?([^\\]<]+)(?:\\]\\]>)?<\\/title>/i;\n const pubDateRegex = /<pubDate>([^<]+)<\\/pubDate>/i;\n const linkRegex = /<link>([^<]+)<\\/link>/i;\n const descRegex = /<description>(?:<!\\[CDATA\\[)?([\\s\\S]*?)(?:\\]\\]>)?<\\/description>/i;\n let match;\n while ((match = itemRegex.exec(xml)) !== null) {\n const block = match[1];\n const t = titleRegex.exec(block);\n const d = pubDateRegex.exec(block);\n const l = linkRegex.exec(block);\n const s = descRegex.exec(block);\n if (t) {\n const pubDate = d ? new Date(d[1]) : null;\n if (!pubDate || pubDate >= cutoff) {\n items.push({ source: 'newsroom', title: t[1].trim(), url: l ? l[1].trim() : '', published: d ? d[1].trim() : null, summary: s ? s[1].replace(/<[^>]+>/g, '').trim().substring(0, 300) : '' });\n }\n }\n }\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'newsroom_rss', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-google-news-company",
"name": "Google News - Company",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
-140
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "=https://news.google.com/rss/search?q={{ encodeURIComponent('\"Acme Services\"') }}&hl=en-AU&gl=AU&ceid=AU:en",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"notes": "Replace the quoted query with your own company name. No API key needed; no published SLA either - see README design notes."
},
{
"id": "parse-google-news-company",
"name": "Parse Google News - Company",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
-140
],
"parameters": {
"jsCode": "const windowDays = 7;\nconst now = new Date();\nconst cutoff = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\nlet items = [];\ntry {\n const xml = $input.first().json.data || '';\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/gi;\n const titleRegex = /<title>(?:<!\\[CDATA\\[)?([^\\]<]+)(?:\\]\\]>)?<\\/title>/i;\n const pubDateRegex = /<pubDate>([^<]+)<\\/pubDate>/i;\n const linkRegex = /<link>([^<]+)<\\/link>/i;\n let match;\n while ((match = itemRegex.exec(xml)) !== null) {\n const block = match[1];\n const t = titleRegex.exec(block);\n const d = pubDateRegex.exec(block);\n const l = linkRegex.exec(block);\n if (t && d) {\n const pubDate = new Date(d[1]);\n if (pubDate >= cutoff) {\n items.push({ source: 'google_news_company', title: t[1].trim(), url: l ? l[1].trim() : '', published: d[1].trim(), summary: '' });\n }\n }\n }\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'google_news_company', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-google-news-competitor",
"name": "Google News - Competitor",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
20
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "=https://news.google.com/rss/search?q={{ encodeURIComponent('\"Northbridge Group\" infrastructure') }}&hl=en-AU&gl=AU&ceid=AU:en",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"notes": "Duplicate this node once per tracked competitor. Replace the quoted query."
},
{
"id": "parse-google-news-competitor",
"name": "Parse Google News - Competitor",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
20
],
"parameters": {
"jsCode": "const windowDays = 7;\nconst now = new Date();\nconst cutoff = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\nlet items = [];\ntry {\n const xml = $input.first().json.data || '';\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/gi;\n const titleRegex = /<title>(?:<!\\[CDATA\\[)?([^\\]<]+)(?:\\]\\]>)?<\\/title>/i;\n const pubDateRegex = /<pubDate>([^<]+)<\\/pubDate>/i;\n const linkRegex = /<link>([^<]+)<\\/link>/i;\n let match;\n while ((match = itemRegex.exec(xml)) !== null) {\n const block = match[1];\n const t = titleRegex.exec(block);\n const d = pubDateRegex.exec(block);\n const l = linkRegex.exec(block);\n if (t && d) {\n const pubDate = new Date(d[1]);\n if (pubDate >= cutoff) {\n items.push({ source: 'google_news_competitor', title: t[1].trim(), url: l ? l[1].trim() : '', published: d[1].trim(), summary: '' });\n }\n }\n }\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'google_news_competitor', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-tender-portal",
"name": "Tender Portal (AU example: AusTender)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
180
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "https://api.tenders.gov.au/ocds/findByDates/publishDate/{from}/{to}",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"notes": "AU example (AusTender OCDS API). For NZ use GETS RSS; for US/UK/EU swap in SAM.gov, Contracts Finder, or TED - see radar.yaml.example comments."
},
{
"id": "parse-tender-portal",
"name": "Parse Tender Portal",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
180
],
"parameters": {
"jsCode": "let items = [];\ntry {\n const data = $input.first().json;\n const releases = data.releases || data.results || data.data || [];\n const arr = Array.isArray(releases) ? releases : [];\n items = arr.map(r => ({ source: 'tender_portal', title: (r.tender && r.tender.title) || '', url: (r.tender && r.tender.id) || '', published: r.date || null, summary: (r.tender && r.tender.description) || '' }));\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'tender_portals', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-exchange",
"name": "Exchange Announcements",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
340
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "https://example.com/asx-api/announcements?ticker=YOUR_TICKER",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"notes": "Replace with your own exchange announcement feed (own ticker and/or listed competitors/clients)."
},
{
"id": "parse-exchange",
"name": "Parse Exchange Announcements",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
340
],
"parameters": {
"jsCode": "let items = [];\ntry {\n const data = $input.first().json;\n const arr = data.data || data.items || [];\n items = (Array.isArray(arr) ? arr : []).map(a => ({ source: 'exchange_announcements', title: a.headline || a.title || '', url: a.url || '', published: a.date || null, summary: '' }));\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'exchange_announcements', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-trade-press",
"name": "Trade Press RSS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
500
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "https://example.com/trade-press/feed",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"notes": "Replace with the trade/industry publication feeds relevant to your sectors. Duplicate for more than one."
},
{
"id": "parse-trade-press",
"name": "Parse Trade Press",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
500
],
"parameters": {
"jsCode": "const windowDays = 7;\nconst now = new Date();\nconst cutoff = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\nlet items = [];\ntry {\n const xml = $input.first().json.data || '';\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/gi;\n const titleRegex = /<title>(?:<!\\[CDATA\\[)?([^\\]<]+)(?:\\]\\]>)?<\\/title>/i;\n const pubDateRegex = /<pubDate>([^<]+)<\\/pubDate>/i;\n const linkRegex = /<link>([^<]+)<\\/link>/i;\n let match;\n while ((match = itemRegex.exec(xml)) !== null) {\n const block = match[1];\n const t = titleRegex.exec(block);\n const d = pubDateRegex.exec(block);\n const l = linkRegex.exec(block);\n if (t && d) {\n const pubDate = new Date(d[1]);\n if (pubDate >= cutoff) {\n items.push({ source: 'trade_press', title: t[1].trim(), url: l ? l[1].trim() : '', published: d[1].trim(), summary: '' });\n }\n }\n }\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'trade_press_rss', data: items, count: items.length, error: null } }];"
}
},
{
"id": "src-regulator",
"name": "Regulator Media Releases",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
460,
660
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "https://example.com/regulator/media-releases/rss",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"notes": "Replace with your sector or competition regulator's media release feed."
},
{
"id": "parse-regulator",
"name": "Parse Regulator",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
660
],
"parameters": {
"jsCode": "const windowDays = 7;\nconst now = new Date();\nconst cutoff = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\nlet items = [];\ntry {\n const xml = $input.first().json.data || '';\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/gi;\n const titleRegex = /<title>(?:<!\\[CDATA\\[)?([^\\]<]+)(?:\\]\\]>)?<\\/title>/i;\n const pubDateRegex = /<pubDate>([^<]+)<\\/pubDate>/i;\n const linkRegex = /<link>([^<]+)<\\/link>/i;\n let match;\n while ((match = itemRegex.exec(xml)) !== null) {\n const block = match[1];\n const t = titleRegex.exec(block);\n const d = pubDateRegex.exec(block);\n const l = linkRegex.exec(block);\n if (t && d) {\n const pubDate = new Date(d[1]);\n if (pubDate >= cutoff) {\n items.push({ source: 'regulator', title: t[1].trim(), url: l ? l[1].trim() : '', published: d[1].trim(), summary: '' });\n }\n }\n }\n} catch (e) { items = []; }\nreturn [{ json: { branch: 'regulator_rss', data: items, count: items.length, error: null } }];"
}
},
{
"id": "build-prompt",
"name": "Build Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1000,
180
],
"parameters": {
"jsCode": "const allItems = $input.all();\nconst branchData = {};\nconst failedBranches = [];\nfor (const item of allItems) {\n const j = item.json;\n if (j && j.branch) {\n branchData[j.branch] = j;\n if (j.error) failedBranches.push(j.branch);\n }\n}\nconst company = 'Acme Services'; // replace with your company name\nconst weekEnding = new Date().toLocaleDateString('en-AU', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\nconst dataStr = JSON.stringify(branchData, null, 2);\nconst instructions = 'You are a market intelligence analyst for ' + company + '. Use only the evidence below. Cite every claim with its source URL. Label each claim confirmed, reported, or speculated. If a value is not disclosed, say so - never estimate. Never use an em dash (Unicode U+2014); use a colon, a comma, or split the sentence. Produce a markdown brief with sections: Executive summary, ' + company + ' direct, Competitors, Tenders and awards, Clients and policy, So what, Sources. Then a fenced json code block with {\"events\": [...]}, each event with keys date, type, entity, headline, value, source_url, confidence, as_at.';\nconst prompt = instructions + '\\n\\nWeek ending: ' + weekEnding + '\\n\\nEVIDENCE:\\n' + dataStr;\nreturn [{ json: { prompt, weekEnding, failedBranches, sourcesScanned: Object.keys(branchData).length } }];"
}
},
{
"id": "llm-synthesis",
"name": "LLM Synthesis (Anthropic)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1250,
180
],
"continueOnFail": true,
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "content-type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 4000, messages: [{ role: 'user', content: $json.prompt }] }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"timeout": 120000
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"notes": "Create a Header Auth credential named 'Anthropic API': header name x-api-key, value your Anthropic API key. See n8n/README.md."
},
{
"id": "format-brief",
"name": "Format Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1500,
180
],
"parameters": {
"jsCode": "const response = $input.first().json;\nconst promptData = $('Build Prompt').first().json;\nlet text = '';\nlet inputTokens = 0;\nlet outputTokens = 0;\ntry {\n text = response.content[0].text;\n inputTokens = response.usage ? response.usage.input_tokens : 0;\n outputTokens = response.usage ? response.usage.output_tokens : 0;\n} catch (e) {\n text = '# Error\\nSynthesis failed: ' + JSON.stringify(response);\n}\nconst fenceMatch = /```json\\s*([\\s\\S]*?)```/g;\nlet lastMatch = null;\nlet m;\nwhile ((m = fenceMatch.exec(text)) !== null) { lastMatch = m; }\nlet brief = text;\nlet events = [];\nif (lastMatch) {\n brief = text.slice(0, lastMatch.index).trim();\n try { events = (JSON.parse(lastMatch[1]).events) || []; } catch (e) { events = []; }\n}\nconst dateStr = new Date().toISOString().slice(0, 10);\nconst filename = dateStr + '-brief.md';\nreturn [{ json: { filename, brief, events, weekEnding: promptData.weekEnding, inputTokens, outputTokens, path: 'reports/' + filename } }];"
}
},
{
"id": "sink-commit",
"name": "Commit Brief (git sink, optional)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1750,
80
],
"continueOnFail": true,
"parameters": {
"method": "PUT",
"url": "=https://api.github.com/repos/YOUR_GITHUB_USERNAME/YOUR_REPORTS_REPO/contents/reports/{{ $json.filename }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "content-type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ message: 'Weekly brief - ' + $json.filename, content: Buffer.from($json.brief).toString('base64'), branch: 'main' }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"timeout": 30000
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"notes": "Optional. Create a Header Auth credential named 'GitHub API': header name Authorization, value 'token <your fine-grained PAT>'. Replace the repo path. Delete this node if you only want the local Python runner's git sink instead."
},
{
"id": "sink-chat",
"name": "Chat Notification (optional)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1750,
280
],
"continueOnFail": true,
"parameters": {
"method": "POST",
"url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ chat_id: $env.TELEGRAM_CHAT_ID, text: 'Market radar brief ready: ' + $json.filename + '\\n' + $json.events.length + ' event(s) found.' }) }}",
"options": {
"timeout": 15000
}
},
"notes": "Optional. Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID as n8n environment variables (Settings > Variables), never hardcoded. Delete this node if unused."
}
],
"connections": {
"Weekly Schedule Trigger": {
"main": [
[
{
"node": "Start Fan-Out",
"type": "main",
"index": 0
}
]
]
},
"Start Fan-Out": {
"main": [
[
{
"node": "Company Newsroom RSS",
"type": "main",
"index": 0
},
{
"node": "Google News - Company",
"type": "main",
"index": 0
},
{
"node": "Google News - Competitor",
"type": "main",
"index": 0
},
{
"node": "Tender Portal (AU example: AusTender)",
"type": "main",
"index": 0
},
{
"node": "Exchange Announcements",
"type": "main",
"index": 0
},
{
"node": "Trade Press RSS",
"type": "main",
"index": 0
},
{
"node": "Regulator Media Releases",
"type": "main",
"index": 0
}
]
]
},
"Company Newsroom RSS": {
"main": [
[
{
"node": "Parse Newsroom",
"type": "main",
"index": 0
}
]
]
},
"Parse Newsroom": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Google News - Company": {
"main": [
[
{
"node": "Parse Google News - Company",
"type": "main",
"index": 0
}
]
]
},
"Parse Google News - Company": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Google News - Competitor": {
"main": [
[
{
"node": "Parse Google News - Competitor",
"type": "main",
"index": 0
}
]
]
},
"Parse Google News - Competitor": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Tender Portal (AU example: AusTender)": {
"main": [
[
{
"node": "Parse Tender Portal",
"type": "main",
"index": 0
}
]
]
},
"Parse Tender Portal": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Exchange Announcements": {
"main": [
[
{
"node": "Parse Exchange Announcements",
"type": "main",
"index": 0
}
]
]
},
"Parse Exchange Announcements": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Trade Press RSS": {
"main": [
[
{
"node": "Parse Trade Press",
"type": "main",
"index": 0
}
]
]
},
"Parse Trade Press": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Regulator Media Releases": {
"main": [
[
{
"node": "Parse Regulator",
"type": "main",
"index": 0
}
]
]
},
"Parse Regulator": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build Prompt": {
"main": [
[
{
"node": "LLM Synthesis (Anthropic)",
"type": "main",
"index": 0
}
]
]
},
"LLM Synthesis (Anthropic)": {
"main": [
[
{
"node": "Format Brief",
"type": "main",
"index": 0
}
]
]
},
"Format Brief": {
"main": [
[
{
"node": "Commit Brief (git sink, optional)",
"type": "main",
"index": 0
},
{
"node": "Chat Notification (optional)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": ""
},
"staticData": null,
"tags": [
{
"name": "market-radar"
}
],
"triggerCount": 1
}
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.
httpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Market Radar - Weekly Intelligence. Uses httpRequest. Scheduled trigger; 21 nodes.
Source: https://github.com/Jiplet/market-radar/blob/main/n8n/market-radar.workflow.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.
Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.
This template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n
Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.
Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.
As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o