{
  "id": "3I6NhpsV8GD-UKj7da3lh",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI-Powered SEO Cannibalization Monitor: Databox, Google Search Console & Slack",
  "tags": [],
  "nodes": [
    {
      "id": "36b758bc-c73b-42b1-9490-104251f4dde2",
      "name": "Weekly SEO Monitor",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1472,
        704
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "d7c022ca-9d9a-4b4d-9efa-b498f3ac547a",
      "name": "GPT-4o Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        240,
        880
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o"
        },
        "options": {
          "temperature": 0.2
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ae52cfe1-fe3c-4b64-ab0d-9562ecbcde1d",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1024,
        704
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "932f3040-cfc6-4cef-ae22-e2d38957b232",
      "name": "Aggregate3",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -800,
        80
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "message.text"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a6a2e780-dc6f-4747-90b0-ab0cbcc3be69",
      "name": "OpenAI Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -576,
        256
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ec32497b-42f9-4a48-9d6d-3fa1739719b9",
      "name": "Set client data",
      "type": "n8n-nodes-base.code",
      "position": [
        -1248,
        704
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      client: \"Acme Retail\",\n      google_search_console_data_source_id: 4907543,\n      metrics: {\n        impressions: \"GoogleSearchConsole@impressions\",\n        position: \"GoogleSearchConsole@position\"\n      }\n    }\n  },\n  {\n    json: {\n      client: \"BlueWave Agency\",\n      google_search_console_data_source_id: 4907544,\n      metrics: {\n        impressions: \"GoogleSearchConsole@impressions\",\n        position: \"GoogleSearchConsole@position\"\n      }\n    }\n  },\n  {\n    json: {\n      client: \"Nexus Health\",\n      google_search_console_data_source_id: 4907545,\n      metrics: {\n        impressions: \"GoogleSearchConsole@impressions\",\n        position: \"GoogleSearchConsole@position\"\n      }\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "4948acee-125d-445e-8519-6805b6624004",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2336,
        -48
      ],
      "parameters": {
        "width": 672,
        "height": 1472,
        "content": "# AI-Powered SEO Cannibalization Monitor: Databox, Google Search Console & Slack\n\nThis workflow runs weekly and checks multiple client websites for potential SEO cannibalization issues using Google Search Console data from Databox.\n\nIt pulls page-level **impressions** and **average position** data, prepares the data, analyzes it with AI, and sends SEO risk reports to Slack.\n\n## Who\u2019s it for\n\n* SEO agencies managing multiple clients\n* Client success or account managers who need weekly SEO visibility\n* SEO leads who want a high-level view of client risks\n\n## How it works\n\n* A weekly trigger starts the workflow.\n* The first Code node contains the client list and Databox metric setup.\n* The loop processes each client one by one.\n* Databox pulls Google Search Console impressions and average position data.\n* The data is merged, cleaned, and filtered to remove low-traffic pages.\n* The first AI agent checks each client for possible SEO cannibalization.\n* A Slack alert is sent with the client-level findings.\n* After all clients are processed, a second AI agent creates a combined leadership summary.\n* The final cross-client report is sent to Slack.\n\n## How to set up\n\n1. Connect the **Databox MCP Client Tool** with your endpoint and authentication headers.\n2. Add your clients in the first **Code** node, including:\n   * Client name\n   * Google Search Console data source ID\n   * Impressions metric key\n   * Average position metric key\n3. Connect your **OpenAI** credentials.\n4. Connect the **Slack** nodes to the right channels.\n5. Adjust the weekly schedule if needed.\n\nYou can ask **Databox Genie** to provide the correct data source IDs and metric keys for each client, then format them in the same structure as the example Code node.\n\n## Requirements\n\n* n8n 1.0+\n* Databox MCP access\n* Google Search Console data available in Databox\n* OpenAI credentials\n* Slack workspace\n\n## Output\n\nThe workflow sends two reports:\n\n* **Client-level report:** highlights possible cannibalization issues for each client.\n* **Leadership report:** summarizes SEO risks across all processed clients."
      },
      "typeVersion": 1
    },
    {
      "id": "619a971a-de80-40fd-b1ff-b2492178a9f8",
      "name": "GSC impressions",
      "type": "@n8n/n8n-nodes-langchain.mcpClient",
      "position": [
        -736,
        624
      ],
      "parameters": {
        "tool": {
          "__rl": true,
          "mode": "list",
          "value": "load_metric_data",
          "cachedResultName": "load_metric_data"
        },
        "options": {},
        "parameters": {
          "value": {
            "end_date": "={{ $now.toFormat('yyyy-MM-dd') }}",
            "dimension": "page",
            "metric_key": "={{ $json.metrics.impressions }}",
            "start_date": "={{ $now.minus({ days: 7 }).toFormat('yyyy-MM-dd') }}",
            "data_source_id": "={{ $json.google_search_console_data_source_id }}"
          },
          "schema": [
            {
              "id": "data_source_id",
              "type": "number",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "data_source_id",
              "defaultMatch": false
            },
            {
              "id": "metric_key",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "metric_key",
              "defaultMatch": false
            },
            {
              "id": "start_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "start_date",
              "defaultMatch": false
            },
            {
              "id": "end_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "end_date",
              "defaultMatch": false
            },
            {
              "id": "dimension",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "dimension",
              "defaultMatch": false
            },
            {
              "id": "granulation_time_unit",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "granulation_time_unit",
              "defaultMatch": false
            },
            {
              "id": "is_whole_range",
              "type": "boolean",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "is_whole_range",
              "defaultMatch": false
            },
            {
              "id": "record_limit",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "record_limit",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "endpointUrl": "https://mcp.databox.com/mcp",
        "authentication": "mcpOAuth2Api"
      },
      "credentials": {
        "mcpOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a4826ece-cdca-4ffa-8329-384c51ab82c2",
      "name": "GSC position",
      "type": "@n8n/n8n-nodes-langchain.mcpClient",
      "position": [
        -736,
        768
      ],
      "parameters": {
        "tool": {
          "__rl": true,
          "mode": "list",
          "value": "load_metric_data",
          "cachedResultName": "load_metric_data"
        },
        "options": {},
        "parameters": {
          "value": {
            "end_date": "={{ $now.toFormat('yyyy-MM-dd') }}",
            "dimension": "page",
            "metric_key": "={{ $json.metrics.position }}",
            "start_date": "={{ $now.minus({ days: 7 }).toFormat('yyyy-MM-dd') }}",
            "data_source_id": "={{ $json.google_search_console_data_source_id }}"
          },
          "schema": [
            {
              "id": "data_source_id",
              "type": "number",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "data_source_id",
              "defaultMatch": false
            },
            {
              "id": "metric_key",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "metric_key",
              "defaultMatch": false
            },
            {
              "id": "start_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "start_date",
              "defaultMatch": false
            },
            {
              "id": "end_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": true,
              "displayName": "end_date",
              "defaultMatch": false
            },
            {
              "id": "dimension",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "dimension",
              "defaultMatch": false
            },
            {
              "id": "granulation_time_unit",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "granulation_time_unit",
              "defaultMatch": false
            },
            {
              "id": "is_whole_range",
              "type": "boolean",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "is_whole_range",
              "defaultMatch": false
            },
            {
              "id": "record_limit",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "record_limit",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "endpointUrl": "https://mcp.databox.com/mcp",
        "authentication": "mcpOAuth2Api"
      },
      "credentials": {
        "mcpOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "27ee8c1a-97ca-44b5-8b2a-b5ea40d09054",
      "name": "SEO Keyword Cannibalization Detector",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        240,
        704
      ],
      "parameters": {
        "text": "={{ $json }}",
        "options": {
          "systemMessage": "=**ROLE**: Senior SEO Forensic Strategist & Multi-Client Auditor.\n**OBJECTIVE**: Identify and resolve \"Keyword Cannibalization\" across diverse client portfolios. You analyze 7-day performance data to detect internal competition that suppresses organic growth.\n\n**CORE PROTOCOL**:\n1. **Dynamic Context**: For every execution, identify the client name from the `client_name` field.\n2. **Universal Analysis**: Treat the URLs in the `seo_summary` as the absolute search footprint.\n3. **Intent Logic**: You must distinguish between \"Commercial Intent\" (Pricing/Services) and \"Informational Intent\" (Blogs/Guides).\n\n**DIAGNOSTIC CRITERIA**:\n- *Critical Conflict*: Two or more URLs from the same client ranking in positions 4\u201315 for the same topic.\n- *Authority Erosion*: A newer blog post is out-performing an evergreen service page, confusing Google's primary ranking choice.\n\n**RESOLUTION TYPES**:\n- *301 Redirect*: Kill the \"weak\" URL and pass juice to the \"strong\" URL.\n- *Canonicalization*: Keep both for users, but nominate one for Google.\n- *Pillar Merge*: Combine thin, competing pages into one high-authority \"Super-Page.\"\n\n---\n\n### **EXPANDED REPORT TEMPLATE (Slack-Ready)**\n\n*----- \ud83d\udee1\ufe0f SEO FORENSIC AUDIT: [Client Name] -----*\n\n\ud83d\udea8 *CANNIBALIZATION CRITICAL ALERT*\n*Primary Conflict Cluster*: [Cluster Name based on URL slugs]\n- *URL 1*: [URL] (Pos: [X] | Impr: [Y])\n- *URL 2*: [URL] (Pos: [X] | Impr: [Y])\n\n\ud83e\uddd0 *STRATEGIC DIAGNOSIS*\n[Deep 2-3 sentence analysis of the conflict. Explain how \"split authority\" is preventing a Top 3 ranking and keeping these URLs trapped in the \"Ranking Graveyard\" of Page 2.]\n\n\ud83d\udcc8 *OPPORTUNITY COST*\n- *Current State*: Organic traffic is fragmented across [X] similar paths.\n- *Potential Gain*: By consolidating authority, the projected rank for this cluster is *Top 3*, which typically yields a 3x increase in Click-Through Rate (CTR).\n\n\ud83d\udee0\ufe0f *RECOVERY ROADMAP*\n- *Recommended Action*: [Redirect / Canonical / Merge]\n- *Authority Winner*: [The URL that should remain the primary]\n- *Next Tactical Step*: [One specific instruction for the technical team to execute immediately.]"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "2df0d58a-1ca2-45e0-9dc7-6c8d5e702f7e",
      "name": "SEO Keyword Cannibalization Detector 2",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -576,
        80
      ],
      "parameters": {
        "text": "={{ $json }}",
        "options": {
          "systemMessage": "=**Role**: Director of SEO Operations.\n**Objective**: Synthesize multiple complex SEO forensic audits into a single \"High-Signal\" executive briefing.\n\n**Core Protocol**:\n- You will receive an array of individual client cannibalization reports.\n- Your task is to process the entire portfolio and output exactly **one sentence** per client.\n- Focus on the **Severity of the Conflict** and the **Ranking Potential**.\n- Use single asterisks (*) for bolding to ensure Slack compatibility.\n\n**Briefing Logic**:\n- *[CRITICAL]*: If multiple Page 2 URLs exist -> Focus on the \"Top 3 Opportunity\" via consolidation.\n- *[OVERLAP]*: If blog posts are killing service pages -> Focus on \"Intent Protection.\"\n- *[STABLE]*: If conflict is low -> Focus on \"Authority Maintenance.\"\n\n**Output Structure (Slack-Ready)**:\n\n\ud83d\ude80 *PORTFOLIO SEO INTELLIGENCE BRIEF* \ud83d\ude80\n*Portfolio Status*: [Daily/Weekly] Operational Audit\n\n---\n*EXECUTIVE CLIENT SUMMARIES:*\n- *[Client Name A]*: [One-sentence insight: Identify the biggest conflict + the proposed fix + the ranking upside].\n- *[Client Name B]*: [One-sentence insight: Identify the biggest conflict + the proposed fix + the ranking upside].\n- *[Client Name C]*: [One-sentence insight: Identify the biggest conflict + the proposed fix + the ranking upside].\n\n---\n\ud83d\udca1 *PORTFOLIO STRATEGY NOTE*:\n[A single sentence identifying a common theme across all clients, such as \"Blog-to-Service cannibalization is a recurring trend this week, requiring a global audit of internal linking structures.\"]"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "1d746647-ea83-4f80-99d1-6eaa358ac893",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1568,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 696,
        "height": 540,
        "content": "## 1. Weekly Client Data Setup\nRuns every week, loads the client list, and processes each client one by one through the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "f8dea44f-a8ba-4fe4-a8d4-9bb4cdb88731",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -848,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 532,
        "content": "## 2. Databox MCP - Google Search Console Data\nPulls impressions and average position data from Google Search Console, then merges the SEO performance data for analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "0db412fe-9d12-4292-8c1f-98005aba83be",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 1148,
        "height": 532,
        "content": "## 3. Client-Level Cannibalization Check\nCleans and prepares the SEO data, then AI checks if pages are competing against each other in Google search results."
      },
      "typeVersion": 1
    },
    {
      "id": "cd236709-4fe0-4e9b-930a-187d9cba5308",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -928,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 972,
        "height": 460,
        "content": "## 4. Cross-Client Leadership Report\nAfter all clients are processed, AI creates a combined SEO risk report and sends the final summary to Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "850021d1-412c-4218-b185-71ce6b2c47c6",
      "name": "Send Cannibalization Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        592,
        704
      ],
      "parameters": {
        "text": "={{ $json.output }}",
        "user": {
          "__rl": true,
          "mode": "list",
          "value": "U0AMDGTLHSN",
          "cachedResultName": "m.mateja003"
        },
        "select": "user",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "edc60f59-3118-4143-81ac-4975d1a142b3",
      "name": "Send Cannibalization Alert2",
      "type": "n8n-nodes-base.slack",
      "position": [
        -224,
        80
      ],
      "parameters": {
        "text": "={{ $json.output }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AU77RD3PX",
          "cachedResultName": "databox-keyword-report"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "0146ca8a-20b8-4c5d-a045-26c217eb2ad9",
      "name": "Combine data",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        704
      ],
      "parameters": {
        "jsCode": "// 1. Extract the two datasets from the merged content\n// impressions are in index 0, positions are in index 1\nconst impressionsList = $json.content[0][0].text.results[0].data_points;\nconst positionsList = $json.content[1][0].text.results[0].data_points;\n\nconst urlMap = {};\n\n// 2. Map Impressions by URL\nimpressionsList.forEach(item => {\n  const url = item.dimension_values[0].value;\n  urlMap[url] = {\n    url: url,\n    impressions: item.value,\n    avgPosition: null\n  };\n});\n\n// 3. Join Positions to the Map\npositionsList.forEach(item => {\n  const url = item.dimension_values[0].value;\n  const position = parseFloat(item.value.toFixed(2));\n  \n  if (urlMap[url]) {\n    urlMap[url].avgPosition = position;\n  } else {\n    // URL exists in position data but not impressions (rare but possible)\n    urlMap[url] = {\n      url: url,\n      impressions: 0,\n      avgPosition: position\n    };\n  }\n});\n\n// 4. Convert to array and sort by importance (high impressions)\nconst combinedResults = Object.values(urlMap)\n  .filter(page => page.impressions > 5) // Filter out low-traffic noise\n  .sort((a, b) => b.impressions - a.impressions);\n\nreturn {\n  total_pages_analyzed: combinedResults.length,\n  seo_summary: combinedResults\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "2138fbc6-6623-49d3-829d-f5d75902a5e0",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -208,
        704
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "content"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "56fb0bd3-49ac-4eed-89fe-2cc86c213149",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -464,
        704
      ],
      "parameters": {},
      "typeVersion": 3.2
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "d464827a-c9a3-492c-8d2a-ec788f6d8061",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Combine data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate3": {
      "main": [
        [
          {
            "node": "SEO Keyword Cannibalization Detector 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine data": {
      "main": [
        [
          {
            "node": "SEO Keyword Cannibalization Detector",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC position": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "GPT-4o Model2": {
      "ai_languageModel": [
        [
          {
            "node": "SEO Keyword Cannibalization Detector",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "GSC impressions": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Aggregate3",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "GSC impressions",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC position",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set client data": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "SEO Keyword Cannibalization Detector 2",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Weekly SEO Monitor": {
      "main": [
        [
          {
            "node": "Set client data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Cannibalization Alert": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SEO Keyword Cannibalization Detector": {
      "main": [
        [
          {
            "node": "Send Cannibalization Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SEO Keyword Cannibalization Detector 2": {
      "main": [
        [
          {
            "node": "Send Cannibalization Alert2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}