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": "PHM1QYKaaGf3X480",
"name": "Reputation Engine \u2014 SEO Research Agent",
"description": null,
"active": true,
"isArchived": false,
"nodes": [
{
"id": "seo-cron",
"name": "Monday Cron Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
200,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 14 * * 1"
}
]
}
}
},
{
"id": "seo-manual",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
200,
100
],
"parameters": {}
},
{
"id": "seo-webhook",
"name": "SEO Research Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
500
],
"parameters": {
"path": "seo-research",
"httpMethod": "POST",
"responseMode": "lastNode",
"options": {}
}
},
{
"id": "build-queries",
"name": "Build Search Queries",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
300
],
"parameters": {
"jsCode": "// Build targeted SEO research queries \u2014 emit one item per query\nconst today = new Date();\nconst isFirstMonday = today.getDate() <= 7;\nconst year = today.getFullYear();\nconst month = today.toLocaleString('en-US', { month: 'long' });\nconst research_date = today.toISOString().split('T')[0];\n\nconst staticData = $getWorkflowStaticData('global');\nconst tavily_key = staticData.tavily_key;\nif (!tavily_key) throw new Error('tavily_key not set in staticData');\n\nconst queries = [\n { label: 'algorithm_updates', query: `Google algorithm update core update spam update ${month} ${year}`, days: 10, max_results: 8 },\n { label: 'healthcare_seo', query: `healthcare SEO EEAT physician authorship medical website ranking ${year}`, days: 14, max_results: 8 },\n { label: 'aeo_schema', query: `AI Overviews healthcare featured snippets structured data schema markup ${year}`, days: 14, max_results: 8 },\n { label: 'personal_brand_serp', query: `personal brand SERP strategy reputation management physician online presence ${year}`, days: 14, max_results: 6 },\n];\n\nreturn queries.map(q => ({\n json: {\n is_monthly: isFirstMonday,\n research_date,\n label: q.label,\n tavily_body: JSON.stringify({\n api_key: tavily_key,\n query: q.query,\n search_depth: 'advanced',\n max_results: q.max_results,\n days: q.days,\n include_answer: true,\n include_raw_content: false,\n }),\n }\n}));"
}
},
{
"id": "tavily-search",
"name": "Tavily SEO Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
800,
300
],
"parameters": {
"method": "POST",
"url": "https://api.tavily.com/search",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ $json.tavily_body }}",
"options": {
"timeout": 30000
}
}
},
{
"id": "build-openclaw",
"name": "Build OpenClaw Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1100,
300
],
"parameters": {
"jsCode": "const upstream = $('Build Search Queries').first().json;\nconst tavily = $json;\nconst research_date = upstream.research_date;\nconst is_monthly = upstream.is_monthly;\nconst total_sources = tavily.total_sources || 0;\n\n// Build structured source blocks per query category\nconst sourceBlocks = (tavily.search_results || []).map(cat => {\n const summary = cat.answer ? `Summary: ${cat.answer}` : '';\n const results = (cat.results || []).map(r => {\n const date = r.published_date ? ` (${r.published_date})` : '';\n return ` - \"${r.title}\"${date}\\n URL: ${r.url}\\n ${(r.content || '').slice(0, 300)}`;\n }).join('\\n');\n return `### ${cat.label.toUpperCase()}\\n${summary}\\n${results}`;\n}).join('\\n\\n');\n\nconst briefType = is_monthly ? 'MONTHLY PLAYBOOK + WEEKLY BRIEF' : 'WEEKLY BRIEF';\n\nconst prompt = `You are the SEO Research Agent for the Reputation Engine.\nDate: ${research_date}\nBrief type: ${briefType}\nTotal sources analyzed: ${total_sources}\n\nSYSTEM CONTEXT:\n- 4 owned domains for Dr. Sina Bari, MD (sinabarimd.com, sinabari.net, sinabariplasticsurgery.com, drsinabari.com)\n- Strategy: entity consolidation + SERP displacement for branded query \"Sina Bari MD\"\n- Content types: medical authority articles, healthcare AI analysis, plastic surgery education\n- Schema: Person+ProfilePage on canonical (sinabarimd.com), WebSite on satellites, Article/MedicalWebPage on articles\n- All articles have FAQPage schema, author bylines, og:article meta, canonical URLs\n- Publishing cadence: ~4 articles/week across 3 active sites\n\nRESEARCH FINDINGS BY CATEGORY:\n${sourceBlocks}\n\nSYNTHESIS RULES:\n- Every claim MUST cite the specific source URL and publication date that supports it\n- If no source supports a claim, do not include it\n- Distinguish between confirmed algorithm changes (official Google announcements) vs. community speculation\n- Be specific: name the update, date, and measurable impact \u2014 not \"Google is favoring E-E-A-T\"\n- For recommendations, cite what source led to that recommendation\n- Flag any findings that directly conflict with each other\n\nOutput valid JSON with these fields:\n{\n \"brief_type\": \"weekly\",\n \"research_date\": \"${research_date}\",\n \"total_sources\": ${total_sources},\n \"algorithm_updates\": [{ \"update\": \"description with specifics\", \"impact\": \"high/medium/low\", \"action_required\": \"what to do\", \"source_url\": \"url\", \"source_date\": \"date\" }],\n \"ranking_signals\": [{ \"signal\": \"description\", \"relevance\": \"how this applies to our network\", \"priority\": \"high/medium/low\", \"source_url\": \"url\" }],\n \"content_recommendations\": [{ \"recommendation\": \"specific advice\", \"applies_to\": \"all/sinabarimd/sinabari_net/sinabariplasticsurgery/drsinabari\", \"urgency\": \"immediate/next_cycle/backlog\", \"rationale\": \"why, citing source\" }],\n \"schema_updates\": [{ \"change\": \"what changed\", \"action\": \"what we should do\", \"source_url\": \"url\" }],\n \"competitive_intel\": \"brief summary of relevant competitive landscape\",\n \"aeo_opportunities\": [\"specific opportunities with source backing\"],\n \"source_quality\": { \"total\": ${total_sources}, \"recent_7d\": 0, \"recent_14d\": 0, \"authoritative\": 0 },\n \"summary\": \"3-4 sentence executive summary with specific findings, not generic advice\"\n}\n\nOutput valid JSON only. No markdown, no commentary.`;\n\nconst openclaw_request_body = JSON.stringify({\n model: 'openclaw',\n input: prompt,\n});\n\nreturn [{ json: {\n research_date,\n is_monthly,\n total_sources,\n openclaw_request_body,\n openclawAgentId: 'seo-research-agent',\n} }];"
}
},
{
"id": "openclaw-synth",
"name": "OpenClaw Synthesize",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1400,
300
],
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:18789/v1/responses",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_OPENCLAW_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-openclaw-agent-id",
"value": "={{ $json.openclawAgentId }}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "JSON",
"body": "={{ $json.openclaw_request_body }}",
"options": {
"timeout": 180000,
"response": {
"response": {
"responseFormat": "json"
}
}
}
}
},
{
"id": "extract-brief",
"name": "Extract Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1700,
300
],
"parameters": {
"jsCode": "const ctx = $('Build OpenClaw Request').first().json;\nconst raw = $json;\n\nlet rawContent;\n\n// Handle both parsed JSON and raw response object\nif (raw.output && Array.isArray(raw.output)) {\n // Already parsed JSON\n rawContent = raw.output[0]?.content?.[0]?.text || '';\n} else if (raw._readableState && raw._readableState.buffer) {\n // Raw response object - extract from buffer\n try {\n const bufferData = raw._readableState.buffer;\n let chunks = [];\n if (Array.isArray(bufferData)) {\n chunks = bufferData;\n } else if (bufferData.head) {\n let node = bufferData.head;\n while (node) {\n chunks.push(node.data || node.value);\n node = node.next;\n }\n }\n const bodyStr = chunks.map(c => {\n if (c && c.type === 'Buffer' && Array.isArray(c.data)) {\n return String.fromCharCode(...c.data);\n }\n return typeof c === 'string' ? c : '';\n }).join('');\n const parsed = JSON.parse(bodyStr);\n rawContent = parsed.output?.[0]?.content?.[0]?.text || '';\n } catch(e) {\n return [{ json: { error: true, message: 'Could not parse buffer response: ' + e.message, raw: JSON.stringify(raw).slice(0, 500) } }];\n }\n} else {\n // Try to find text in any format\n rawContent = typeof raw === 'string' ? raw : JSON.stringify(raw);\n}\n\nlet brief;\ntry {\n brief = JSON.parse(rawContent);\n} catch (e) {\n const jsonMatch = rawContent.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (jsonMatch) {\n brief = JSON.parse(jsonMatch[1].trim());\n } else {\n const objMatch = rawContent.match(/\\{[\\s\\S]*\\}/);\n if (objMatch) {\n brief = JSON.parse(objMatch[0]);\n } else {\n return [{ json: { error: true, message: 'Could not parse brief from OpenClaw response', raw: rawContent.slice(0, 500) } }];\n }\n }\n}\n\nbrief.research_date = ctx.research_date;\nbrief.is_monthly = ctx.is_monthly;\nbrief.total_sources = ctx.total_sources;\nbrief.generated_at = new Date().toISOString();\n\nreturn [{ json: brief }];"
}
},
{
"id": "store-brief",
"name": "Store Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
300
],
"parameters": {
"jsCode": "const brief = $json;\nconst staticData = $getWorkflowStaticData('global');\n\n// Store latest brief\nstaticData.latest_brief = brief;\n\n// Keep history (last 12 briefs)\nif (!staticData.brief_history) staticData.brief_history = [];\nstaticData.brief_history.push({\n research_date: brief.research_date,\n brief_type: brief.brief_type,\n summary: brief.summary,\n generated_at: brief.generated_at,\n algorithm_updates_count: (brief.algorithm_updates || []).length,\n recommendations_count: (brief.content_recommendations || []).length,\n});\nif (staticData.brief_history.length > 12) staticData.brief_history = staticData.brief_history.slice(-12);\n\nreturn [{ json: brief }];"
}
},
{
"id": "queue-to-orch",
"name": "Queue to Orchestrator",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2300,
300
],
"parameters": {
"method": "POST",
"url": "https://n8n.sinabarimd.com/webhook/store-seo-intel",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"timeout": 15000
}
},
"onError": "continueRegularOutput"
},
{
"id": "seo-respond",
"name": "Respond",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2600,
300
],
"parameters": {
"jsCode": "const brief = $('Store Brief').first().json;\nreturn [{ json: {\n success: true,\n research_date: brief.research_date,\n brief_type: brief.brief_type,\n summary: brief.summary,\n algorithm_updates: (brief.algorithm_updates || []).length,\n recommendations: (brief.content_recommendations || []).length,\n generated_at: brief.generated_at,\n} }];"
}
},
{
"id": "get-intel-webhook",
"name": "Get Intel Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
700
],
"parameters": {
"path": "seo-intel",
"httpMethod": "GET",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "return-intel",
"name": "Return Intel",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
700
],
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\nreturn [{ json: {\n latest_brief: staticData.latest_brief || null,\n brief_history: staticData.brief_history || [],\n} }];"
}
},
{
"id": "respond-intel",
"name": "Respond Intel",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
800,
700
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
}
},
{
"id": "collect-tavily-results",
"name": "Collect Tavily Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
600,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Collect all Tavily results into a single structured object\nconst items = $input.all();\nconst research_date = items[0].json.research_date || items[0].json.is_monthly !== undefined \n ? (items[0].json.research_date || new Date().toISOString().split('T')[0])\n : new Date().toISOString().split('T')[0];\nconst is_monthly = items[0].json.is_monthly || false;\n\nconst search_results = items.map(item => {\n const label = item.json.label || 'unknown';\n const results = (item.json.results || []).map(r => ({\n title: r.title,\n url: r.url,\n content: (r.content || '').slice(0, 400),\n published_date: r.published_date || null,\n score: r.score || null,\n }));\n return {\n label,\n answer: item.json.answer || null,\n result_count: results.length,\n results,\n };\n});\n\nconst total_sources = search_results.reduce((sum, r) => sum + r.result_count, 0);\n\nreturn [{ json: {\n research_date,\n is_monthly,\n search_results,\n total_sources,\n} }];"
}
}
],
"connections": {
"Monday Cron Trigger": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"SEO Research Webhook": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Build Search Queries": {
"main": [
[
{
"node": "Tavily SEO Search",
"type": "main",
"index": 0
}
]
]
},
"Tavily SEO Search": {
"main": [
[
{
"node": "Collect Tavily Results",
"type": "main",
"index": 0
}
]
]
},
"Build OpenClaw Request": {
"main": [
[
{
"node": "OpenClaw Synthesize",
"type": "main",
"index": 0
}
]
]
},
"OpenClaw Synthesize": {
"main": [
[
{
"node": "Extract Brief",
"type": "main",
"index": 0
}
]
]
},
"Extract Brief": {
"main": [
[
{
"node": "Store Brief",
"type": "main",
"index": 0
}
]
]
},
"Store Brief": {
"main": [
[
{
"node": "Queue to Orchestrator",
"type": "main",
"index": 0
}
]
]
},
"Queue to Orchestrator": {
"main": [
[
{
"node": "Respond",
"type": "main",
"index": 0
}
]
]
},
"Get Intel Webhook": {
"main": [
[
{
"node": "Return Intel",
"type": "main",
"index": 0
}
]
]
},
"Return Intel": {
"main": [
[
{
"node": "Respond Intel",
"type": "main",
"index": 0
}
]
]
},
"Collect Tavily Results": {
"main": [
[
{
"node": "Build OpenClaw Request",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"meta": null,
"activeVersionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"versionCounter": 61,
"triggerCount": 3,
"shared": [
{
"updatedAt": "2026-04-27T18:53:47.798Z",
"createdAt": "2026-04-27T18:53:47.798Z",
"role": "workflow:owner",
"workflowId": "PHM1QYKaaGf3X480",
"projectId": "9sJSA5GTLSjQcRNk",
"project": {
"updatedAt": "2026-03-20T18:09:16.655Z",
"createdAt": "2026-03-20T00:15:30.157Z",
"id": "9sJSA5GTLSjQcRNk",
"name": "Sina Bari <YOUR_EMAIL@example.com>",
"type": "personal",
"icon": null,
"description": null,
"creatorId": "d84a1587-61fd-429c-9ea6-1d21d8267ea9"
}
}
],
"tags": [],
"activeVersion": {
"updatedAt": "2026-04-27T19:31:58.372Z",
"createdAt": "2026-04-27T19:31:58.372Z",
"versionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"workflowId": "PHM1QYKaaGf3X480",
"nodes": [
{
"id": "seo-cron",
"name": "Monday Cron Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
200,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 14 * * 1"
}
]
}
}
},
{
"id": "seo-manual",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
200,
100
],
"parameters": {}
},
{
"id": "seo-webhook",
"name": "SEO Research Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
500
],
"webhookId": "seo-research",
"parameters": {
"path": "seo-research",
"httpMethod": "POST",
"responseMode": "lastNode",
"options": {}
}
},
{
"id": "build-queries",
"name": "Build Search Queries",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
300
],
"parameters": {
"jsCode": "// Build targeted SEO research queries \u2014 emit one item per query\nconst today = new Date();\nconst isFirstMonday = today.getDate() <= 7;\nconst year = today.getFullYear();\nconst month = today.toLocaleString('en-US', { month: 'long' });\nconst research_date = today.toISOString().split('T')[0];\n\nconst staticData = $getWorkflowStaticData('global');\nconst tavily_key = staticData.tavily_key;\nif (!tavily_key) throw new Error('tavily_key not set in staticData');\n\nconst queries = [\n { label: 'algorithm_updates', query: `Google algorithm update core update spam update ${month} ${year}`, days: 10, max_results: 8 },\n { label: 'healthcare_seo', query: `healthcare SEO EEAT physician authorship medical website ranking ${year}`, days: 14, max_results: 8 },\n { label: 'aeo_schema', query: `AI Overviews healthcare featured snippets structured data schema markup ${year}`, days: 14, max_results: 8 },\n { label: 'personal_brand_serp', query: `personal brand SERP strategy reputation management physician online presence ${year}`, days: 14, max_results: 6 },\n];\n\nreturn queries.map(q => ({\n json: {\n is_monthly: isFirstMonday,\n research_date,\n label: q.label,\n tavily_body: JSON.stringify({\n api_key: tavily_key,\n query: q.query,\n search_depth: 'advanced',\n max_results: q.max_results,\n days: q.days,\n include_answer: true,\n include_raw_content: false,\n }),\n }\n}));"
}
},
{
"id": "tavily-search",
"name": "Tavily SEO Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
800,
300
],
"parameters": {
"method": "POST",
"url": "https://api.tavily.com/search",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ $json.tavily_body }}",
"options": {
"timeout": 30000
}
}
},
{
"id": "build-openclaw",
"name": "Build OpenClaw Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1100,
300
],
"parameters": {
"jsCode": "const upstream = $('Build Search Queries').first().json;\nconst tavily = $json;\nconst research_date = upstream.research_date;\nconst is_monthly = upstream.is_monthly;\nconst total_sources = tavily.total_sources || 0;\n\n// Build structured source blocks per query category\nconst sourceBlocks = (tavily.search_results || []).map(cat => {\n const summary = cat.answer ? `Summary: ${cat.answer}` : '';\n const results = (cat.results || []).map(r => {\n const date = r.published_date ? ` (${r.published_date})` : '';\n return ` - \"${r.title}\"${date}\\n URL: ${r.url}\\n ${(r.content || '').slice(0, 300)}`;\n }).join('\\n');\n return `### ${cat.label.toUpperCase()}\\n${summary}\\n${results}`;\n}).join('\\n\\n');\n\nconst briefType = is_monthly ? 'MONTHLY PLAYBOOK + WEEKLY BRIEF' : 'WEEKLY BRIEF';\n\nconst prompt = `You are the SEO Research Agent for the Reputation Engine.\nDate: ${research_date}\nBrief type: ${briefType}\nTotal sources analyzed: ${total_sources}\n\nSYSTEM CONTEXT:\n- 4 owned domains for Dr. Sina Bari, MD (sinabarimd.com, sinabari.net, sinabariplasticsurgery.com, drsinabari.com)\n- Strategy: entity consolidation + SERP displacement for branded query \"Sina Bari MD\"\n- Content types: medical authority articles, healthcare AI analysis, plastic surgery education\n- Schema: Person+ProfilePage on canonical (sinabarimd.com), WebSite on satellites, Article/MedicalWebPage on articles\n- All articles have FAQPage schema, author bylines, og:article meta, canonical URLs\n- Publishing cadence: ~4 articles/week across 3 active sites\n\nRESEARCH FINDINGS BY CATEGORY:\n${sourceBlocks}\n\nSYNTHESIS RULES:\n- Every claim MUST cite the specific source URL and publication date that supports it\n- If no source supports a claim, do not include it\n- Distinguish between confirmed algorithm changes (official Google announcements) vs. community speculation\n- Be specific: name the update, date, and measurable impact \u2014 not \"Google is favoring E-E-A-T\"\n- For recommendations, cite what source led to that recommendation\n- Flag any findings that directly conflict with each other\n\nOutput valid JSON with these fields:\n{\n \"brief_type\": \"weekly\",\n \"research_date\": \"${research_date}\",\n \"total_sources\": ${total_sources},\n \"algorithm_updates\": [{ \"update\": \"description with specifics\", \"impact\": \"high/medium/low\", \"action_required\": \"what to do\", \"source_url\": \"url\", \"source_date\": \"date\" }],\n \"ranking_signals\": [{ \"signal\": \"description\", \"relevance\": \"how this applies to our network\", \"priority\": \"high/medium/low\", \"source_url\": \"url\" }],\n \"content_recommendations\": [{ \"recommendation\": \"specific advice\", \"applies_to\": \"all/sinabarimd/sinabari_net/sinabariplasticsurgery/drsinabari\", \"urgency\": \"immediate/next_cycle/backlog\", \"rationale\": \"why, citing source\" }],\n \"schema_updates\": [{ \"change\": \"what changed\", \"action\": \"what we should do\", \"source_url\": \"url\" }],\n \"competitive_intel\": \"brief summary of relevant competitive landscape\",\n \"aeo_opportunities\": [\"specific opportunities with source backing\"],\n \"source_quality\": { \"total\": ${total_sources}, \"recent_7d\": 0, \"recent_14d\": 0, \"authoritative\": 0 },\n \"summary\": \"3-4 sentence executive summary with specific findings, not generic advice\"\n}\n\nOutput valid JSON only. No markdown, no commentary.`;\n\nconst openclaw_request_body = JSON.stringify({\n model: 'openclaw',\n input: prompt,\n});\n\nreturn [{ json: {\n research_date,\n is_monthly,\n total_sources,\n openclaw_request_body,\n openclawAgentId: 'seo-research-agent',\n} }];"
}
},
{
"id": "openclaw-synth",
"name": "OpenClaw Synthesize",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1400,
300
],
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:18789/v1/responses",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_OPENCLAW_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-openclaw-agent-id",
"value": "={{ $json.openclawAgentId }}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "JSON",
"body": "={{ $json.openclaw_request_body }}",
"options": {
"timeout": 180000,
"response": {
"response": {
"responseFormat": "json"
}
}
}
}
},
{
"id": "extract-brief",
"name": "Extract Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1700,
300
],
"parameters": {
"jsCode": "const ctx = $('Build OpenClaw Request').first().json;\nconst raw = $json;\n\nlet rawContent;\n\n// Handle both parsed JSON and raw response object\nif (raw.output && Array.isArray(raw.output)) {\n // Already parsed JSON\n rawContent = raw.output[0]?.content?.[0]?.text || '';\n} else if (raw._readableState && raw._readableState.buffer) {\n // Raw response object - extract from buffer\n try {\n const bufferData = raw._readableState.buffer;\n let chunks = [];\n if (Array.isArray(bufferData)) {\n chunks = bufferData;\n } else if (bufferData.head) {\n let node = bufferData.head;\n while (node) {\n chunks.push(node.data || node.value);\n node = node.next;\n }\n }\n const bodyStr = chunks.map(c => {\n if (c && c.type === 'Buffer' && Array.isArray(c.data)) {\n return String.fromCharCode(...c.data);\n }\n return typeof c === 'string' ? c : '';\n }).join('');\n const parsed = JSON.parse(bodyStr);\n rawContent = parsed.output?.[0]?.content?.[0]?.text || '';\n } catch(e) {\n return [{ json: { error: true, message: 'Could not parse buffer response: ' + e.message, raw: JSON.stringify(raw).slice(0, 500) } }];\n }\n} else {\n // Try to find text in any format\n rawContent = typeof raw === 'string' ? raw : JSON.stringify(raw);\n}\n\nlet brief;\ntry {\n brief = JSON.parse(rawContent);\n} catch (e) {\n const jsonMatch = rawContent.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (jsonMatch) {\n brief = JSON.parse(jsonMatch[1].trim());\n } else {\n const objMatch = rawContent.match(/\\{[\\s\\S]*\\}/);\n if (objMatch) {\n brief = JSON.parse(objMatch[0]);\n } else {\n return [{ json: { error: true, message: 'Could not parse brief from OpenClaw response', raw: rawContent.slice(0, 500) } }];\n }\n }\n}\n\nbrief.research_date = ctx.research_date;\nbrief.is_monthly = ctx.is_monthly;\nbrief.total_sources = ctx.total_sources;\nbrief.generated_at = new Date().toISOString();\n\nreturn [{ json: brief }];"
}
},
{
"id": "store-brief",
"name": "Store Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
300
],
"parameters": {
"jsCode": "const brief = $json;\nconst staticData = $getWorkflowStaticData('global');\n\n// Store latest brief\nstaticData.latest_brief = brief;\n\n// Keep history (last 12 briefs)\nif (!staticData.brief_history) staticData.brief_history = [];\nstaticData.brief_history.push({\n research_date: brief.research_date,\n brief_type: brief.brief_type,\n summary: brief.summary,\n generated_at: brief.generated_at,\n algorithm_updates_count: (brief.algorithm_updates || []).length,\n recommendations_count: (brief.content_recommendations || []).length,\n});\nif (staticData.brief_history.length > 12) staticData.brief_history = staticData.brief_history.slice(-12);\n\nreturn [{ json: brief }];"
}
},
{
"id": "queue-to-orch",
"name": "Queue to Orchestrator",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2300,
300
],
"parameters": {
"method": "POST",
"url": "https://n8n.sinabarimd.com/webhook/store-seo-intel",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"timeout": 15000
}
},
"onError": "continueRegularOutput"
},
{
"id": "seo-respond",
"name": "Respond",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2600,
300
],
"parameters": {
"jsCode": "const brief = $('Store Brief').first().json;\nreturn [{ json: {\n success: true,\n research_date: brief.research_date,\n brief_type: brief.brief_type,\n summary: brief.summary,\n algorithm_updates: (brief.algorithm_updates || []).length,\n recommendations: (brief.content_recommendations || []).length,\n generated_at: brief.generated_at,\n} }];"
}
},
{
"id": "get-intel-webhook",
"name": "Get Intel Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
700
],
"webhookId": "seo-intel",
"parameters": {
"path": "seo-intel",
"httpMethod": "GET",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "return-intel",
"name": "Return Intel",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
700
],
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\nreturn [{ json: {\n latest_brief: staticData.latest_brief || null,\n brief_history: staticData.brief_history || [],\n} }];"
}
},
{
"id": "respond-intel",
"name": "Respond Intel",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
800,
700
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
}
},
{
"id": "collect-tavily-results",
"name": "Collect Tavily Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
600,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Collect all Tavily results into a single structured object\nconst items = $input.all();\nconst research_date = items[0].json.research_date || items[0].json.is_monthly !== undefined \n ? (items[0].json.research_date || new Date().toISOString().split('T')[0])\n : new Date().toISOString().split('T')[0];\nconst is_monthly = items[0].json.is_monthly || false;\n\nconst search_results = items.map(item => {\n const label = item.json.label || 'unknown';\n const results = (item.json.results || []).map(r => ({\n title: r.title,\n url: r.url,\n content: (r.content || '').slice(0, 400),\n published_date: r.published_date || null,\n score: r.score || null,\n }));\n return {\n label,\n answer: item.json.answer || null,\n result_count: results.length,\n results,\n };\n});\n\nconst total_sources = search_results.reduce((sum, r) => sum + r.result_count, 0);\n\nreturn [{ json: {\n research_date,\n is_monthly,\n search_results,\n total_sources,\n} }];"
}
}
],
"connections": {
"Monday Cron Trigger": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"SEO Research Webhook": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Build Search Queries": {
"main": [
[
{
"node": "Tavily SEO Search",
"type": "main",
"index": 0
}
]
]
},
"Tavily SEO Search": {
"main": [
[
{
"node": "Collect Tavily Results",
"type": "main",
"index": 0
}
]
]
},
"Build OpenClaw Request": {
"main": [
[
{
"node": "OpenClaw Synthesize",
"type": "main",
"index": 0
}
]
]
},
"OpenClaw Synthesize": {
"main": [
[
{
"node": "Extract Brief",
"type": "main",
"index": 0
}
]
]
},
"Extract Brief": {
"main": [
[
{
"node": "Store Brief",
"type": "main",
"index": 0
}
]
]
},
"Store Brief": {
"main": [
[
{
"node": "Queue to Orchestrator",
"type": "main",
"index": 0
}
]
]
},
"Queue to Orchestrator": {
"main": [
[
{
"node": "Respond",
"type": "main",
"index": 0
}
]
]
},
"Get Intel Webhook": {
"main": [
[
{
"node": "Return Intel",
"type": "main",
"index": 0
}
]
]
},
"Return Intel": {
"main": [
[
{
"node": "Respond Intel",
"type": "main",
"index": 0
}
]
]
},
"Collect Tavily Results": {
"main": [
[
{
"node": "Build OpenClaw Request",
"type": "main",
"index": 0
}
]
]
}
},
"authors": "Sina Bari",
"name": null,
"description": null,
"autosaved": false,
"workflowPublishHistory": [
{
"createdAt": "2026-04-27T19:31:58.449Z",
"id": 64,
"workflowId": "PHM1QYKaaGf3X480",
"versionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"event": "activated",
"userId": "d84a1587-61fd-429c-9ea6-1d21d8267ea9"
},
{
"createdAt": "2026-04-27T19:31:59.329Z",
"id": 66,
"workflowId": "PHM1QYKaaGf3X480",
"versionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"event": "activated",
"userId": "d84a1587-61fd-429c-9ea6-1d21d8267ea9"
},
{
"createdAt": "2026-04-27T19:31:58.423Z",
"id": 63,
"workflowId": "PHM1QYKaaGf3X480",
"versionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"event": "deactivated",
"userId": "d84a1587-61fd-429c-9ea6-1d21d8267ea9"
},
{
"createdAt": "2026-04-27T19:31:59.302Z",
"id": 65,
"workflowId": "PHM1QYKaaGf3X480",
"versionId": "a0cee02e-b43a-47b5-aceb-c6095752826f",
"event": "deactivated",
"userId": "d84a1587-61fd-429c-9ea6-1d21d8267ea9"
}
]
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates SEO research to enhance your online reputation by generating targeted search queries, fetching real-time results via Tavily, and synthesising insights from web sources using OpenClaw's API. It suits digital marketers, SEO specialists, or business owners aiming to monitor and improve search visibility without manual effort. The key step involves the code node that extracts concise briefs from synthesised data, delivering actionable intelligence on brand mentions and competitor strategies.
Use this workflow for routine reputation audits, such as weekly scans of your domain's SEO footprint, or to track emerging trends in your niche. Avoid it for one-off queries better handled by manual tools, or if you need deep AI-driven analysis beyond basic synthesis. Common variations include adjusting the cron schedule for daily runs or integrating additional HTTP requests to pull data from specific sites like Google Alerts.
About this workflow
Reputation Engine — SEO Research Agent. Uses httpRequest. Scheduled trigger; 15 nodes.
Source: https://github.com/sinabarimd/reputation-engine/blob/main/workflows/seo-research-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.
Master Agent - Orchestrator. Uses httpRequest, telegram, telegramTrigger. Scheduled trigger; 46 nodes.
Reputation Engine — Content Research Agent. Uses httpRequest. Scheduled trigger; 45 nodes.
Master Agent - Orchestrator. Uses httpRequest, telegram, telegramTrigger. Scheduled trigger; 43 nodes.
Linkedin Workflow. Uses httpRequest, googleSheets. Scheduled trigger; 39 nodes.
I prepared a detailed guide that shows the whole process of building an AI tool to analyze Instagram Reels using n8n.