{
  "name": "10 - Business Landscape Tracker",
  "tags": [],
  "nodes": [
    {
      "id": "1be5e364-b985-4ce4-bb52-5f4d4afc8481",
      "name": "\ud83d\udd52 Auto Run (Scheduled)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2040,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7e5d5597-0e13-477d-af55-2847cdd4b89c",
      "name": "\ud83d\udcc4 Read Companies Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1820,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
          "cachedResultName": "Companies List"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "0942f8f7-220c-48fc-bda0-43b250981475",
      "name": "\ud83e\uddf9 Clean & Format Company List",
      "type": "n8n-nodes-base.code",
      "position": [
        -1600,
        0
      ],
      "parameters": {
        "jsCode": "const companies = [];\n\nfor (const item of $input.all()) {\n  // Get company name from the correct field key: \"List\"\n  const companyName = item.json.List;\n\n  if (companyName && companyName.trim() !== '') {\n    companies.push({\n      company: companyName.trim(),\n      row: item.json.row_number || companies.length + 2\n    });\n  }\n}\n\n// Return each company as a separate item\nreturn companies.map(company => ({ json: company }));"
      },
      "typeVersion": 2
    },
    {
      "id": "dd0127ef-28ef-4928-8cdc-6cf0627ac365",
      "name": "\ud83d\udd01 Loop Over Companies",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1380,
        0
      ],
      "parameters": {
        "options": {},
        "batchSize": 100
      },
      "typeVersion": 3
    },
    {
      "id": "ee903349-6643-4c97-862c-bd50d2abcd8d",
      "name": "\ud83c\udf0d Search Company Competitors (SerpAPI)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1160,
        0
      ],
      "parameters": {
        "url": "https://serpapi.com/search.json",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.company }} competitors"
            },
            {
              "name": "hl",
              "value": "en"
            },
            {
              "name": "gl",
              "value": "us"
            },
            {
              "name": "api_key",
              "value": "YOUR_SERPAPI_KEY_HERE"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a5f9d7d7-a7b2-4325-bbe9-c2feb053f98a",
      "name": "\ud83e\udde0 Extract Competitor Data from Search",
      "type": "n8n-nodes-base.code",
      "position": [
        -940,
        0
      ],
      "parameters": {
        "jsCode": "const inputData = $input.all();\nconst results = [];\n\nfor (const item of inputData) {\n  const searchResults = item.json;\n\n  // === Helper Functions (same as before) ===\n\n  function extractCompanyName(searchData) {\n    if (searchData.search_parameters?.q) {\n      const query = searchData.search_parameters.q.toLowerCase();\n      const cleanQuery = query\n        .replace(/competitors?/gi, '')\n        .replace(/competition/gi, '')\n        .replace(/vs/gi, '')\n        .replace(/alternatives?/gi, '')\n        .trim();\n      const companyName = cleanQuery.split(' ')[0];\n      return companyName.charAt(0).toUpperCase() + companyName.slice(1);\n    }\n\n    if (searchData.organic_results?.[0]?.title) {\n      const title = searchData.organic_results[0].title;\n      const match = title.match(/(\\w+)\\s+competitors?/i);\n      if (match) return match[1];\n    }\n\n    return \"Unknown Company\";\n  }\n\n  function extractCompetitors(searchData) {\n    const competitors = new Set();\n\n    if (searchData.related_questions) {\n      searchData.related_questions.forEach(q => {\n        q.list?.forEach(item => {\n          const clean = item.replace(/\\.$/, '').trim();\n          if (clean.length > 1) competitors.add(clean);\n        });\n      });\n    }\n\n    if (searchData.ai_overview?.text_blocks) {\n      searchData.ai_overview.text_blocks.forEach(block => {\n        block.list?.forEach(item => {\n          const clean = item.title?.replace(/:$/, '').trim();\n          if (clean?.length > 1) competitors.add(clean);\n        });\n      });\n    }\n\n    if (searchData.organic_results) {\n      searchData.organic_results.forEach(result => {\n        const snippet = result.snippet;\n        if (snippet) {\n          const patterns = [\n            /competitors?[^.]*?include[^.]*?([A-Z][a-zA-Z\\s,&.]+)/gi,\n            /rivals?[^.]*?include[^.]*?([A-Z][a-zA-Z\\s,&.]+)/gi,\n            /competition[^.]*?from[^.]*?([A-Z][a-zA-Z\\s,&.]+)/gi\n          ];\n          patterns.forEach(pattern => {\n            const matches = snippet.match(pattern);\n            if (matches) {\n              matches.forEach(match => {\n                match\n                  .split(/,|\\sand\\s|&/)\n                  .map(name => name.replace(/[^a-zA-Z\\s]/g, '').trim())\n                  .forEach(name => {\n                    if (name.length > 2 && !name.toLowerCase().includes('competitor'))\n                      competitors.add(name);\n                  });\n              });\n            }\n          });\n        }\n      });\n    }\n\n    if (searchData.answer_box?.expanded_list) {\n      searchData.answer_box.expanded_list.forEach(item => {\n        if (item.title) competitors.add(item.title);\n      });\n    }\n\n    return Array.from(competitors).slice(0, 10);\n  }\n\n  function getTopSource(searchData) {\n    return searchData.organic_results?.[0]?.link || null;\n  }\n\n  // === Main Logic ===\n  try {\n    const company = extractCompanyName(searchResults);\n    const competitorsList = extractCompetitors(searchResults);\n    const topSource = getTopSource(searchResults);\n\n    results.push({\n      json: {\n        company,\n        competitors: competitorsList.join(', ') || 'No competitors found',\n        competitor_count: competitorsList.length,\n        top_source: topSource,\n        search_query: searchResults.search_parameters?.q || 'N/A',\n        total_results: searchResults.search_information?.total_results || 0,\n        extraction_timestamp: new Date().toISOString()\n      }\n    });\n  } catch (err) {\n    results.push({\n      json: {\n        error: `Extraction failed: ${err.message}`,\n        company: \"Unknown\",\n        competitors: \"\",\n        competitor_count: 0,\n        top_source: null\n      }\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "20193daa-c689-4311-ad39-9f696794712d",
      "name": "\ud83e\uddd0 Has Competitors?",
      "type": "n8n-nodes-base.if",
      "position": [
        -720,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8a7b6c5d-4321-0987-6543-210fedcba987",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.competitors }}",
              "rightValue": "No competitors found"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1f513736-3963-4045-9eaa-4df3cebfc0e3",
      "name": "\ud83d\udcca Log to Result Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -500,
        -100
      ],
      "parameters": {
        "columns": {
          "value": {
            "Source": "={{ $json.top_source }}",
            "Company": "={{ $json.company }}",
            "Competitors": "={{ $json.competitors }}",
            "Total Results": "={{ $json.total_results }}"
          },
          "schema": [
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Competitors",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Competitors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Results",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Total Results",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Source",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Source",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Company"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true,
          "cellFormat": "USER_ENTERED"
        },
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_RESULTS_GOOGLE_SHEET_ID_HERE/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_RESULTS_GOOGLE_SHEET_ID_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_RESULTS_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
          "cachedResultName": "Companies Result"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "d39b91c7-a3c0-45eb-b926-2d668262a899",
      "name": "\u274c Log Companies Without Results",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -500,
        100
      ],
      "parameters": {
        "columns": {
          "value": {
            "Source": "Null",
            "Company": "={{ $json.company }}",
            "Competitors": "No Competetitors Found",
            "Total Results": "0"
          },
          "schema": [
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Competitors",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Competitors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Results",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Total Results",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Source",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Source",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true,
          "cellFormat": "USER_ENTERED"
        },
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_RESULTS_GOOGLE_SHEET_ID_HERE/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_RESULTS_GOOGLE_SHEET_ID_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_RESULTS_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
          "cachedResultName": "Companies Result"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "7910cb3e-88e7-4716-82ad-89bec10409a8",
      "name": "\ud83d\uddc3\ufe0f Sync to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        -280,
        0
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_AIRTABLE_BASE_ID_HERE",
          "cachedResultUrl": "https://airtable.com/YOUR_AIRTABLE_BASE_ID_HERE",
          "cachedResultName": "Test"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_AIRTABLE_TABLE_ID_HERE",
          "cachedResultUrl": "https://airtable.com/YOUR_AIRTABLE_BASE_ID_HERE/YOUR_AIRTABLE_TABLE_ID_HERE",
          "cachedResultName": "Table 1"
        },
        "columns": {
          "value": {
            "Source": "={{ $json.Source }}",
            "Company": "={{ $json.Company }}",
            "Competitors": "={{ $json.Competitors }}",
            "Total Results": "={{ $json[\"Total Results\"] }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Competitors",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Competitors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Results",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Total Results",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Source",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Source",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Company"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "upsert"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "b299cf8f-6b5e-48f2-adb4-2794ad4220aa",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2140,
        -340
      ],
      "parameters": {
        "color": 5,
        "width": 280,
        "height": 640,
        "content": "## Auto Run (Scheduled)\n\n**Description:**\n\n\u23f0 This is the starting point of the workflow. It runs automatically based on your configured schedule\u2014no manual trigger needed."
      },
      "typeVersion": 1
    },
    {
      "id": "e21ba623-5589-4781-a131-31afbee80431",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1840,
        -340
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 640,
        "content": "## Loop Over Companies\n\n**Description:**\n\n\ud83d\udccb Fetch companies from a Google Sheet (List column).\n\n\ud83e\uddf9 Clean and format data, removing empty entries and attaching row numbers.\n\n\ud83d\udd01 Send companies into the flow using a Lopp Over node for processing.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "38ce6a00-5b57-47a2-9cce-e2deaa67e285",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1220,
        -340
      ],
      "parameters": {
        "color": 4,
        "width": 220,
        "height": 640,
        "content": "## Search Company Competitors (SerpAPI)\n\n**Description:**\n\n\ud83d\udd0d For each company, this node sends a GET request to SerpAPI with the query {company} competitors, returning structured search result JSON for further parsing."
      },
      "typeVersion": 1
    },
    {
      "id": "78b206db-221d-426c-aa15-1115c2266cc8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -980,
        -340
      ],
      "parameters": {
        "width": 420,
        "height": 640,
        "content": "## Extract & Check Competitors\n\n**Description:**\n\nExtracts the company name, up to 10 competitors, and top source from SerpAPI results.\nThen checks if any competitors were found:\n\n\u2705 Yes \u2192 Send to Google Sheet + Airtable\n\n\u274c No \u2192 Log as failed search"
      },
      "typeVersion": 1
    },
    {
      "id": "6ef49329-b2a7-44ad-9226-d2c61dc996e7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -540,
        -340
      ],
      "parameters": {
        "color": 6,
        "width": 460,
        "height": 640,
        "content": "## Log Results to Sheets & Airtable\n\n**Description:**\n\nLogs successful competitor data to Google Sheets \u2705\n\nLogs failed searches separately \u274c\n\nBoth flows sync to Airtable for unified storage \ud83d\udce5"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "\ud83e\uddd0 Has Competitors?": {
      "main": [
        [
          {
            "node": "\ud83d\udcca Log to Result Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u274c Log Companies Without Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcca Log to Result Sheet": {
      "main": [
        [
          {
            "node": "\ud83d\uddc3\ufe0f Sync to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd01 Loop Over Companies": {
      "main": [
        [],
        [
          {
            "node": "\ud83c\udf0d Search Company Competitors (SerpAPI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcc4 Read Companies Sheet": {
      "main": [
        [
          {
            "node": "\ud83e\uddf9 Clean & Format Company List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd52 Auto Run (Scheduled)": {
      "main": [
        [
          {
            "node": "\ud83d\udcc4 Read Companies Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\uddf9 Clean & Format Company List": {
      "main": [
        [
          {
            "node": "\ud83d\udd01 Loop Over Companies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u274c Log Companies Without Results": {
      "main": [
        [
          {
            "node": "\ud83d\uddc3\ufe0f Sync to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Extract Competitor Data from Search": {
      "main": [
        [
          {
            "node": "\ud83e\uddd0 Has Competitors?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udf0d Search Company Competitors (SerpAPI)": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Extract Competitor Data from Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}