> ## Content Index
> Fetch the complete content index at: https://buildwire.ai/llms.txt
> Use this file to discover other available public pages before exploring further.

# 🎯 Advanced Competitor Ads Tracker
- URL: https://buildwire.ai/blog/advanced-competitor-ads-tracker/
- Published: 2025-12-04T13:55:03.000Z
- Updated: 2026-06-14T07:31:22.000Z
- Description: An AI-powered competitive intelligence workflow that automatically discovers competitors, scrapes their advertising campaigns across multiple platforms, and delivers comprehensive weekly reports with actionable insights.
- Author: Rananjay Raj
- Tags: #beehiiv, #Migrated-1781422191461, #Import 2026-06-14 13:00

## 📋 Overview

This n8n workflow automates competitive analysis by:

- **AI-powered competitor discovery** using Claude Sonnet 4
- **Multi-platform ad scraping** (Google Ads, Facebook/Meta, LinkedIn, TikTok)
- **Deep AI analysis** of messaging, keywords, and strategies
- **Automated scoring** of opportunities and threats
- **Executive HTML reports** delivered via email

**Time Savings:** \~4-6 hours per week of manual competitive research

**ROI:** 10-50x through automated insights and faster market response

---

## ✨ Key Features

### 🔍 Intelligent Competitor Discovery

- Claude AI identifies direct competitors based on your business profile
- Focuses on single most relevant competitor per analysis
- Industry-specific competitive intelligence

### 📊 Multi-Platform Ad Monitoring

- **Google Ads Transparency** \- Search and display campaigns
- **Facebook/Meta Ad Library** \- Social advertising strategies
- **LinkedIn Ads** \- B2B professional targeting
- **TikTok Ads** \- Short-form video campaigns

### 🧠 AI-Powered Analysis

- Extracts ad copy, keywords, and messaging themes
- Identifies competitive advantages and market gaps
- Provides actionable recommendations
- Calculates opportunity and threat scores

### 📈 Strategic Scoring System

- **Opportunity Score** (0-100) - Market gaps and growth potential
- **Threat Score** (0-100) - Competitive pressure assessment
- **Priority Levels** \- High/Medium/Low action items
- **Confidence Ratings** \- AI analysis reliability scores

### 📧 Professional Reporting

- Beautifully formatted HTML email reports
- Executive summary with key findings
- Detailed competitive insights
- Strategic recommendations

---

## 🛠️ Prerequisites

### Required Services & API Keys

1. **n8n Instance** (self-hosted or cloud)
  - Version 1.0+ recommended
  - Webhook/form trigger support enabled
2. **Anthropic API Key** (Claude)
  - Model: Claude Sonnet 4 (claude-sonnet-4-20250514)
  - Cost: \~$10-15/month for weekly reports
3. **Apify Account** (Ad Scraping)
  - Free tier: 5 actors, limited runs
  - Paid tier recommended: $49/month for reliable scraping
  - Required Actors:
    - Google Ads Transparency Scraper (shashankms2580/google-ads-transparency-scraper)
    - Facebook Ads Scraper (anchor/facebook-ads-scraper)
    - LinkedIn Ads Scraper (anchor/linkedin-ads-scraper)
    - TikTok Ads Scraper (worldwidestore/tiktok-ads-scraper)
4. **Gmail Account** (Report Delivery)
  - OAuth2 authentication required
  - Setup in n8n credentials manager

---

## 📦 Installation & Setup

### Step 1: Import Workflow

1. Download `Competitor_Ad_Tracking_System_SANITIZED.json`
2. Open your n8n instance
3. Click **Import** → **From File**
4. Select the downloaded JSON file
5. Workflow will appear in your workflows list

### Step 2: Configure Credentials

**IMPORTANT:** Never paste API keys directly into code nodes. Always use n8n’s credential manager.

### A. Anthropic API Credential

1. In n8n, go to **Settings** → **Credentials**
2. Click **Add Credential** → **Anthropic API**
3. Enter your API key from [https://console.anthropic.com/](https://console.anthropic.com/?utm%5Fsource=theaidrivenmarketer.beehiiv.com&utm%5Fmedium=referral&utm%5Fcampaign=advanced-competitor-ads-tracker)
4. Save with name “Anthropic account”

### B. Apify API Credential

1. Go to **Settings** → **Credentials**
2. Click **Add Credential** → **Apify API**
3. Enter your Apify API token from [https://console.apify.com/account/integrations](https://console.apify.com/account/integrations?utm%5Fsource=theaidrivenmarketer.beehiiv.com&utm%5Fmedium=referral&utm%5Fcampaign=advanced-competitor-ads-tracker)
4. Save with name “Apify account”

### C. Gmail OAuth2 Credential

1. Go to **Settings** → **Credentials**
2. Click **Add Credential** → **Gmail OAuth2 API**
3. Follow n8n’s OAuth2 setup wizard
4. Authorize Gmail access
5. Save with name “Gmail account”

### Step 3: Attach Credentials to Nodes

Open the workflow and attach credentials to these nodes:

1. **🔍 AI Competitor Discovery** → Attach “Anthropic account”
2. 🧠 **AI Ad Copy & Keywords Analysis** → Attach “Anthropic account”
3. **Message a model** → Attach “Anthropic account”
4. **Google Ads** → Attach “Apify account”
5. **Facebook Ads Scraper** → Attach “Apify account”
6. **LinkedIn Ads Scraper** → Attach “Apify account”
7. **TikTok Ads Scraper** → Attach “Apify account”
8. 📧 **Send Weekly Intelligence Report** → Attach “Gmail account”

### Step 4: Configure Email Recipient

1. Open the 📧 **Send Weekly Intelligence Report** node
2. Change `sendTo` parameter from `YOUR_EMAIL@company.com` to your actual email
3. Save the node

### Step 5: Activate Workflow

1. Click the **Activate** toggle in the top-right
2. Copy the webhook URL from the **On form submission** node
3. You’re ready to run your first analysis!

---

## 🚀 Usage

### Web Form Method (Recommended)

1. Navigate to your workflow’s webhook URL
2. Fill out the competitive intelligence form:
  - **Business Name**: Your company name
  - **Business Domain**: Your website (e.g., [yourcompany.com](http://yourcompany.com/?utm%5Fsource=theaidrivenmarketer.beehiiv.com&utm%5Fmedium=referral&utm%5Fcampaign=advanced-competitor-ads-tracker))
  - **Industry Keywords**: Comma-separated (e.g., “SaaS, marketing automation, analytics”)
3. Click **Submit**
4. Wait 2-5 minutes for analysis to complete
5. Check your email for the comprehensive report

### Manual Execution

1. Open the workflow in n8n
2. Click **Execute Workflow** button
3. Manually input business details when prompted
4. View results in execution log

---

## 📊 Understanding the Report

Your weekly report includes:

### 1\. Executive Summary

High-level overview of competitive landscape and key findings

### 2\. Competitor Profile

- Name and domain
- Focus area and positioning
- Scan date and metadata

### 3\. Advertising Intelligence

- **Ad Copies**: Actual ads from competitors
- **Keywords**: Target keywords identified
- **Messaging Themes**: Brand messaging patterns
- **Targeting Insights**: Audience demographics

### 4\. Competitive Analysis

- **Advantages**: What competitors do well
- **Opportunity Gaps**: Market openings you can exploit
- **Threat Level**: High/Medium/Low competitive pressure

### 5\. Strategic Scoring

- **Opportunity Score**: Market potential (0-100)
- **Threat Score**: Competitive risk (0-100)
- **Priority Level**: Action urgency
- **Confidence Score**: AI analysis reliability

### 6\. Recommended Actions

Specific, prioritized steps to improve competitive position

---

## 🔧 Configuration Options

### Competitor Discovery Settings

Edit the **🔍 AI Competitor Discovery** node to adjust:

```
{  "model": "claude-sonnet-4-20250514",  "max_tokens": 1000,  "temperature": 0.3  // Lower = more focused, Higher = more creative}

```

**To find multiple competitors:**

- Change system prompt to request “top 3 competitors” instead of “top direct competitor”
- Adjust `max_tokens` to 2000+ for longer responses

### Ad Scraping Limits

Adjust data collection in Apify actor nodes:

**Google Ads node:**

```
{  "maxResults": 20,  // Increase for more ads (costs more)  "region": "US"     // Change for different regions}

```

**Apply similar adjustments to Facebook, LinkedIn, TikTok nodes**

### Analysis Depth

Edit 🧠 **AI Ad Copy & Keywords Analysis** node:

```
{  "max_tokens": 3000,  // More tokens = deeper analysis  "temperature": 0.2   // Lower for consistency, higher for creativity}

```

### Report Frequency

Currently form-triggered. To schedule weekly reports:

1. Replace **On form submission** node with **Schedule Trigger**
2. Set cron expression: `0 9 * * 1` (Every Monday at 9 AM)
3. Add a **Set** node to define business variables

---

## 💰 Cost Breakdown

### Estimated Monthly Costs (Weekly Reports)

| Service                  | Usage                         | Cost              |
| ------------------------ | ----------------------------- | ----------------- |
| **Anthropic Claude API** | \~15K tokens/report × 4 weeks | $10-15            |
| **Apify Actors**         | 4 actors × 4 runs/month       | $49-99            |
| **n8n Cloud** (optional) | Workflow hosting              | $20-50            |
| **Gmail**                | Email delivery                | Free              |
| **Total**                |                               | **$79-164/month** |

### Cost Optimization Tips

1. **Use Apify Free Tier** for testing (5 actors, limited runs)
2. **Reduce ad scraping frequency** to bi-weekly
3. **Self-host n8n** to eliminate hosting costs
4. **Limit Apify maxResults** to reduce actor runtime
5. **Use Claude Haiku** for faster, cheaper analysis (trade-off: less detailed)

---

## 🐛 Troubleshooting

### Common Issues

### 1\. “Paired Item” Errors in Parse AI Analysis

**Solution:** Already fixed in this workflow using `$itemIndex` approach

### 2\. Apify Actors Timeout

**Cause:** Scrapers take too long or hit rate limits **Solution:**

- Increase timeout in Apify node settings
- Reduce `maxResults` parameter
- Enable Apify proxy in actor settings

### 3\. Claude API Returns Empty Response

**Cause:** Invalid JSON formatting or API quota exceeded **Solution:**

- Check Anthropic console for API errors
- Verify credential is correctly attached
- Ensure you have API credits remaining

### 4\. Gmail Not Sending Reports

**Cause:** OAuth2 token expired or insufficient permissions **Solution:**

- Reconnect Gmail OAuth2 credential
- Ensure “Send email” permission is granted
- Check Gmail quota limits (500 emails/day)

### 5\. No Competitors Found

**Cause:** Claude couldn’t identify competitors from input **Solution:**

- Provide more specific industry keywords
- Include detailed business description
- Check Claude API response in execution log

### 6\. Workflow Runs But No Email Received

**Check:**

- Email address is correct in Send Email node
- Gmail credential is properly attached
- Check spam/junk folder
- Review execution log for errors

---

## 🔐 Security Best Practices

### Credential Management

- ✅ **DO:** Store all API keys in n8n’s credential manager
- ✅ **DO:** Use environment variables for sensitive data
- ❌ **DON’T:** Paste API keys directly into Code nodes
- ❌ **DON’T:** Share workflows with credentials attached

### Data Privacy

- **Competitor data** is processed in-memory only
- **No data storage** except in execution logs (configurable)
- **Email reports** contain sensitive competitive intelligence
  - Use encrypted email or secure delivery method
  - Limit recipient list to authorized personnel

### Rotating Credentials

If credentials are compromised:

1. Immediately revoke API keys in respective consoles
2. Generate new keys
3. Update credentials in n8n
4. Review execution logs for unauthorized access

---

## 📈 Advanced Usage

### Integration with Google Sheets

Add a **Google Sheets** node after **Aggregate All Results**:

```
{  "operation": "append",  "sheetId": "YOUR_SHEET_ID",  "range": "A:Z"}

```

Map these fields:

- `scan_date`, `competitor_name`, `competitor_domain`
- `opportunity_score`, `threat_score`, `priority_level`
- `keywords_identified`, `messaging_themes`

### Slack Notifications

Replace or supplement Gmail with **Slack** node:

```
{  "resource": "message",  "operation": "post",  "channel": "#competitive-intel",  "text": "New competitor report ready!"}

```

### Database Storage (PostgreSQL/MySQL)

Add database node for historical tracking:

```
INSERT INTO competitor_tracking (
  scan_date, competitor_name, opportunity_score,
  threat_score, keywords, raw_analysis
) VALUES (?, ?, ?, ?, ?, ?)

```

---

## 🤝 Contributing

Improvements and feature requests are welcome!

**To contribute:**

1. Fork this workflow
2. Make your enhancements
3. Test thoroughly
4. Share sanitized version (no credentials!)
5. Document changes in README

**Feature ideas:**

- Multi-competitor batch analysis
- Historical trend tracking
- Automated A/B test recommendations
- Competitive pricing intelligence
- Social media sentiment analysis

---

## 📄 License

MIT License - Free to use, modify, and distribute

---

## 🆘 Support

**Issues & Questions:**

- Open an issue on GitHub
- Check n8n community forum: [https://community.n8n.io/](https://community.n8n.io/?utm%5Fsource=theaidrivenmarketer.beehiiv.com&utm%5Fmedium=referral&utm%5Fcampaign=advanced-competitor-ads-tracker)
- Review Anthropic documentation: [https://docs.anthropic.com/](https://docs.anthropic.com/?utm%5Fsource=theaidrivenmarketer.beehiiv.com&utm%5Fmedium=referral&utm%5Fcampaign=advanced-competitor-ads-tracker)
- Consult Apify actor documentation

**Professional Support:**

- For custom implementations or enterprise support
- Email: \[your-support-email\]

---

## 💡 Use Cases

### Marketing Teams

- Monitor competitor campaign launches
- Track messaging trends across platforms
- Identify underserved audience segments
- Optimize ad spend based on competitive intelligence

### Product Managers

- Understand competitive feature positioning
- Track competitor product launches
- Identify market gaps for new features
- Monitor competitive pricing strategies

### Agencies

- Deliver competitive intelligence to clients
- Benchmark client performance against industry
- Generate monthly competitive reports
- Support strategic planning with data

### E-commerce

- Track competitor promotional strategies
- Monitor seasonal campaign trends
- Identify winning ad copy patterns
- Optimize product positioning

---

## 🎯 Workflow Architecture

```
Form Trigger
    ↓
AI Competitor Discovery (Claude)
    ↓
Parse JSON Response
    ↓
Parse Competitor Data
    ↓
┌─────────────────────────────────────┐
│  Parallel Ad Scraping (Apify)       │
│  ├─ Google Ads Transparency         │
│  ├─ Facebook/Meta Ad Library        │
│  ├─ LinkedIn Ads                    │
│  └─ TikTok Ads                      │
└─────────────────────────────────────┘
    ↓
Merge All Ad Data
    ↓
AI Analysis (Claude)
    ↓
Parse Analysis Results
    ↓
Competitive Opportunity Scoring
    ↓
Aggregate Results
    ↓
Generate Executive Report (Claude)
    ↓
Send Email Report (Gmail)
```

# Full JSON Code

{ "name": "Competitor Ad Tracking System (FIXED)", "nodes": \[ { "parameters": { "method": "POST", "url": "https://api.anthropic.com/v1/messages", "authentication": "predefinedCredentialType", "nodeCredentialType": "anthropicApi", "sendHeaders": true, "headerParameters": { "parameters": \[ { "name": "anthropic-version", "value": "2023-06-01" } \] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={\\n \\"model\\": \\"claude-sonnet-4-20250514\\",\\n \\"max\_tokens\\": 1000,\\n \\"temperature\\": 0.3,\\n \\"system\\": \\"You are a competitive intelligence analyst. Given a business name, domain, and industry keywords, identify the top direct competitor. Return competitor as a separate JSON object on its own line (newline-delimited JSON format). Do NOT return a JSON array. Strictly stick to only 1 competitor. Each line should be a complete competitor object.\\",\\n \\"messages\\": \[\\n {\\n \\"role\\": \\"user\\",\\n \\"content\\": \\"Business: \\\\nDomain: \\\\nIndustry: \\\\n\\\\nFind top direct competitor and return as a separate JSON object:\\\\n{\\\\\\"name\\\\\\": \\\\\\"CompetitorName\\\\\\", \\\\\\"domain\\\\\\": \\\\\\"competitor.com\\\\\\", \\\\\\"focus\_area\\\\\\": \\\\\\"brief description\\\\\\"}\\"\\n }\\n \]\\n}", "options": {} }, "id": "7200989b-4f1e-4f1e-b196-61054c9c9114", "name": "🔍 AI Competitor Discovery1", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": \[-1904, 1568\] }, { "parameters": { "jsCode": "// Parse newline-delimited JSON from Claude\\nconst response = $input.all()\[0\].json;\\nlet content = '';\\n\\nif (response.content && Array.isArray(response.content)) {\\n content = response.content\[0\]?.text || '';\\n}\\n\\nif (!content) {\\n throw new Error('No content found in Claude response');\\n}\\n\\n// Split by newlines and parse each JSON object\\nconst lines = content.trim().split('\\\\n').filter(line => line.trim());\\nconst competitors = \[\];\\n\\nfor (const line of lines) {\\n try {\\n const competitor = JSON.parse(line.trim());\\n competitors.push(competitor);\\n } catch (e) {\\n console.log('Failed to parse line:', line);\\n }\\n}\\n\\nif (competitors.length === 0) {\\n throw new Error('No competitors parsed from response');\\n}\\n\\nreturn competitors.map(comp => ({ json: comp }));" }, "id": "6362b1c2-7d54-4d78-959f-c08e9491e131", "name": "Parse Newline-Delimited JSON1", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1680, 1568\] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Do not mutate $json or its properties!\\nlet businessName = 'YourBusiness';\\ntry {\\n businessName = $('Setup Business Variables').item.json\['business\_name'\] || 'YourBusiness';\\n} catch (e) {\\n // If reading fails, fallback used\\n}\\n\\n// Safely extract properties, avoiding mutation\\nconst competitorName = typeof $json?.name === 'string' ? $json.name : 'Unknown Competitor';\\nconst competitorDomain = typeof $json?.domain === 'string' ? $json.domain : 'unknown.com';\\nconst focusArea = typeof $json?.focus\_area === 'string' ? $json.focus\_area : 'Unknown';\\n\\nreturn {\\n json: {\\n competitor\_id: $itemIndex + 1,\\n competitor\_name: competitorName,\\n competitor\_domain: competitorDomain,\\n focus\_area: focusArea,\\n business\_name: businessName,\\n scan\_date: new Date().toISOString().split('T')\[0\]\\n }\\n};" }, "id": "b1604d5d-9b2a-4cea-900c-524f78d24f60", "name": "Parse Competitors Data1", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1456, 1568\] }, { "parameters": { "mode": "combine", "combinationMode": "mergeByPosition", "options": {} }, "id": "ff75c4c0-b987-4529-8afe-17f8e16b2d71", "name": "Merge All Ad Data1", "type": "n8n-nodes-base.merge", "typeVersion": 2.1, "position": \[-784, 1568\] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Parse Claude AI analysis and structure the data\\nif (!item || !item.json) {\\n throw new Error('No input item found.');\\n}\\n\\nconst items\_data = item.json;\\n\\n// Claude API returns: { content: \[{ type: \\"text\\", text: \\"...\\" }\] }\\nlet response = '';\\nif (items\_data.content && Array.isArray(items\_data.content)) {\\n response = items\_data.content\[0\]?.text || '';\\n} else if (items\_data.choices && Array.isArray(items\_data.choices)) {\\n response = items\_data.choices\[0\]?.message?.content || '';\\n}\\n\\nif (!response) {\\n throw new Error('No response content found from Claude API.');\\n}\\n\\nlet analysis;\\ntry {\\n analysis = JSON.parse(response);\\n} catch (e) {\\n const jsonMatch = response.match(/\`\`\`(?:json)?\\\\s\*(\[\\\\s\\\\S\]\*?)\\\\s\*\`\`\`/);\\n if (jsonMatch && jsonMatch\[1\]) {\\n analysis = JSON.parse(jsonMatch\[1\].trim());\\n } else {\\n throw new Error(\`Failed to parse AI analysis: ${e.message}\`);\\n }\\n}\\n\\n// --- FIX START ---\\n// Fetch all items from the source node and select the one matching the current index.\\n// This bypasses the \\"Paired Item\\" error caused by broken lineage.\\nconst sourceItems = $('Parse Competitors Data1').all();\\nconst competitorData = (sourceItems\[$itemIndex\] && sourceItems\[$itemIndex\].json) ? sourceItems\[$itemIndex\].json : {};\\n// --- FIX END ---\\n\\nconst enrichedAnalysis = {\\n ...analysis,\\n scan\_date: new Date().toISOString().split('T')\[0\],\\n scan\_timestamp: new Date().toISOString(),\\n business\_name: competitorData.business\_name || 'YourBusiness',\\n competitor\_id: competitorData.competitor\_id || 1,\\n competitor\_domain: competitorData.competitor\_domain || 'unknown.com',\\n focus\_area: competitorData.focus\_area || 'Unknown',\\n raw\_google\_ads: items\_data.google\_ads\_data ? 'Available' : 'Not Available',\\n raw\_meta\_ads: items\_data.meta\_ads\_data ? 'Available' : 'Not Available',\\n raw\_linkedin\_data: items\_data.linkedin\_intelligence ? 'Available' : 'Not Available'\\n};\\n\\nreturn { json: enrichedAnalysis };" }, "id": "7fcea385-ea3a-47d8-93ed-2a1d9d02f4c6", "name": "Parse AI Analysis Results1", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-208, 1568\] }, { "parameters": { "jsCode": "// Calculate competitive opportunity scores\\nconst data = items\[0\].json;\\n\\nlet opportunityScore = 0;\\nlet threatScore = 0;\\n\\nif (data.keywords\_identified && Array.isArray(data.keywords\_identified)) {\\n opportunityScore += Math.min(data.keywords\_identified.length \* 2, 20);\\n}\\n\\nif (data.opportunity\_gaps && Array.isArray(data.opportunity\_gaps)) {\\n opportunityScore += Math.min(data.opportunity\_gaps.length \* 5, 25);\\n}\\n\\nswitch (data.threat\_level) {\\n case 'high': threatScore = 80; break;\\n case 'medium': threatScore = 50; break;\\n case 'low': threatScore = 20; break;\\n default: threatScore = 40;\\n}\\n\\nif (data.competitive\_advantages && Array.isArray(data.competitive\_advantages)) {\\n threatScore += Math.min(data.competitive\_advantages.length \* 3, 20);\\n}\\n\\nconst confidence = parseInt(data.confidence\_score) || 50;\\nopportunityScore = Math.round(opportunityScore \* (confidence / 100));\\nthreatScore = Math.round(threatScore \* (confidence / 100));\\n\\nlet priority = 'medium';\\nif (opportunityScore > 30 || threatScore > 60) {\\n priority = 'high';\\n} else if (opportunityScore < 15 && threatScore < 30) {\\n priority = 'low';\\n}\\n\\nconst scoredData = {\\n ...data,\\n opportunity\_score: Math.min(opportunityScore, 100),\\n threat\_score: Math.min(threatScore, 100),\\n priority\_level: priority,\\n overall\_impact: Math.round((opportunityScore + threatScore) / 2),\\n keywords\_count: data.keywords\_identified ? data.keywords\_identified.length : 0,\\n opportunities\_count: data.opportunity\_gaps ? data.opportunity\_gaps.length : 0,\\n advantages\_count: data.competitive\_advantages ? data.competitive\_advantages.length : 0,\\n scoring\_timestamp: new Date().toISOString()\\n};\\n\\nreturn \[{ json: scoredData }\];" }, "id": "2d3669f5-fd7c-4e36-b5b1-70f3068a45e0", "name": "🎯 Competitive Opportunity Scoring1", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[16, 1568\] }, { "parameters": { "aggregate": "aggregateAllItemData", "options": {} }, "id": "ccc150eb-b8ff-4bed-8a26-5901c47c22b8", "name": "Aggregate All Results1", "type": "n8n-nodes-base.aggregate", "typeVersion": 1, "position": \[240, 1568\] }, { "parameters": { "sendTo": "YOUR\_EMAIL@company.com", "subject": "=🎯 Weekly Competitive Intelligence Report - ", "message": "=", "options": {} }, "id": "98c56140-873c-43fb-8fd7-ed370a308a7f", "name": "📧 Send Weekly Intelligence Report1", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": \[816, 1568\] }, { "parameters": { "formTitle": "Competitor Intelligence Form", "formDescription": "Enter business details for competitive analysis", "formFields": { "values": \[ { "fieldLabel": "Business Name", "requiredField": true }, { "fieldLabel": "Business Domain", "requiredField": true }, { "fieldLabel": "Industry Keywords", "fieldType": "textarea", "requiredField": true } \] }, "responseMode": "lastNode", "options": {} }, "id": "2c8028b2-cd0b-45ec-bc0a-617884cc26f4", "name": "On form submission1", "type": "n8n-nodes-base.formTrigger", "typeVersion": 2.3, "position": \[-2128, 1568\] }, { "parameters": { "operation": "Run actor and get dataset", "actorId": { "\_\_rl": true, "value": "zB0rjv0Wf9gyguGSV", "mode": "list", "cachedResultName": "Google Ads Transparency Scraper (shashankms2580/google-ads-transparency-scraper)", "cachedResultUrl": "https://console.apify.com/actors/zB0rjv0Wf9gyguGSV/input" }, "customBody": "={\\n \\"deltaSinceLastRun\\": true,\\n \\"mode\\": \\"FULL\\",\\n \\"ocrEnabled\\": false,\\n \\"preset\\": \\"competitive\_analysis\\",\\n \\"region\\": \\"US\\",\\n \\"targets\\": \[\\n \\"\\"\\n \]\\n}", "timeout": {} }, "type": "@apify/n8n-nodes-apify.apify", "typeVersion": 1, "position": \[-1232, 1280\], "id": "85c59a15-19f3-4022-930f-7a3e8ac1628d", "name": "Google Ads" }, { "parameters": { "jsCode": "// Get all ads returned by the scraper\\nconst items = $input.all();\\n\\n// If no ads were found, return a default \\"No Data\\" message\\nif (items.length === 0) {\\n return \[{\\n json: {\\n google\_ads\_data: \\"No ads found or scraper returned no data.\\",\\n google\_ads\_count: 0\\n }\\n }\];\\n}\\n\\n// Combine all ad details into a single text summary for the AI\\nlet consolidatedText = \`Found ${items.length} ads.\\\\n\\\\n\`;\\n\\nitems.forEach((item, index) => {\\n const ad = item.json;\\n \\n // Skip if it's just an empty state record\\n if (ad.status\_message === 'empty\_state\_text' || ad.has\_ads === false) {\\n return;\\n }\\n\\n consolidatedText += \`Ad ${index + 1}:\\\\n\`;\\n if (ad.text\_copy) consolidatedText += \`Copy: ${ad.text\_copy}\\\\n\`;\\n if (ad.images && ad.images.length > 0) consolidatedText += \`Image URL: ${ad.images\[0\]}\\\\n\`;\\n if (ad.media\_type) consolidatedText += \`Type: ${ad.media\_type}\\\\n\`;\\n consolidatedText += '---\\\\n';\\n});\\n\\n// Return ONE item with the consolidated data\\nreturn \[{\\n json: {\\n // Keep the competitor name from the first item if available, or pass it through\\n advertiser\_name: items\[0\].json.advertiser\_name,\\n google\_ads\_data: consolidatedText,\\n google\_ads\_count: items.length\\n }\\n}\];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1008, 1280\], "id": "33b7b6b4-19ad-4ed5-a658-10eb3b6044ea", "name": "Code in JavaScript" }, { "parameters": { "jsCode": "// Get all ads returned by the Meta/Facebook scraper\\nconst items = $input.all();\\n\\n// If no data came back, return a default empty state\\nif (items.length === 0) {\\n return \[{\\n json: {\\n meta\_ads\_data: \\"No ads found or scraper returned no data.\\",\\n meta\_ads\_count: 0\\n }\\n }\];\\n}\\n\\n// Initialize the summary string\\nlet consolidatedText = \`Found ${items.length} ads.\\\\n\\\\n\`;\\n\\nitems.forEach((item, index) => {\\n const ad = item.json;\\n \\n // Basic filtering to skip empty/invalid records if necessary\\n if (!ad.ad\_archive\_id && !ad.ad\_body) {\\n return;\\n }\\n\\n consolidatedText += \`Ad ${index + 1}:\\\\n\`;\\n \\n // Meta scraper field names often differ. We check common variations.\\n // 1\. Ad Copy (Body text)\\n const adBody = ad.ad\_body || ad.ad\_creative\_body || ad.body || (ad.snapshot ? ad.snapshot.body : '') || 'No text';\\n if (adBody) consolidatedText += \`Copy: ${adBody}\\\\n\`;\\n \\n // 2\. Headline\\n const headline = ad.ad\_headline || ad.title || (ad.snapshot ? ad.snapshot.title : '');\\n if (headline) consolidatedText += \`Headline: ${headline}\\\\n\`;\\n\\n // 3\. CTA\\n const cta = ad.ad\_creative\_link\_caption || ad.call\_to\_action\_type || (ad.snapshot ? ad.snapshot.cta\_text : '');\\n if (cta) consolidatedText += \`CTA: ${cta}\\\\n\`;\\n\\n // 4\. Image/Video URL (for context)\\n const mediaUrl = ad.image\_url || ad.video\_url || (ad.snapshot && ad.snapshot.images ? ad.snapshot.images\[0\] : '');\\n if (mediaUrl) consolidatedText += \`Media URL: ${mediaUrl}\\\\n\`;\\n \\n // 5\. Active Dates\\n if (ad.start\_date) consolidatedText += \`Started: ${ad.start\_date}\\\\n\`;\\n\\n consolidatedText += '---\\\\n';\\n});\\n\\n// Return ONE item with the consolidated data\\nreturn \[{\\n json: {\\n // Try to grab the page name from the first valid ad\\n advertiser\_name: items\[0\].json.page\_name || items\[0\].json.ad\_creative\_link\_title || \\"Unknown\\",\\n meta\_ads\_data: consolidatedText,\\n meta\_ads\_count: items.length\\n }\\n}\];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1008, 1472\], "id": "950a93ba-c536-487e-ba99-6703189ee99a", "name": "Code in JavaScript1" }, { "parameters": { "jsCode": "// Get all ads returned by the LinkedIn scraper\\nconst items = $input.all();\\n\\n// Safely retrieve the competitor name from the Parse Competitors Data1 node, \\n// which is the source of truth for the competitor's identity in this execution branch.\\nconst competitorName = $node\['Parse Competitors Data1'\].json.competitor\_name || \\"Unknown\\";\\n\\n// If no data came back\\nif (items.length === 0) {\\n return \[{\\n json: {\\n linkedin\_intelligence: \\"No ads found or scraper returned no data.\\",\\n linkedin\_ads\_count: 0,\\n company\_name: competitorName // Use the safely retrieved name\\n }\\n }\];\\n}\\n\\nlet consolidatedText = \`Found ${items.length} LinkedIn ads for ${competitorName}.\\\\n\\\\n\`;\\n\\nitems.forEach((item, index) => {\\n const ad = item.json;\\n\\n consolidatedText += \`Ad ${index + 1}:\\\\n\`;\\n\\n // 1\. Main Text\\n const mainText = ad.text || ad.description || ad.commentary || '';\\n if (mainText) consolidatedText += \`Text: ${mainText}\\\\n\`;\\n\\n // 2\. Headline (often appears in the link preview)\\n const headline = ad.headline || ad.title || '';\\n if (headline) consolidatedText += \`Headline: ${headline}\\\\n\`;\\n\\n // 3\. Landing Page / URL\\n const url = ad.landing\_page\_url || ad.destination\_url || ad.reference\_link || '';\\n if (url) consolidatedText += \`Link: ${url}\\\\n\`;\\n\\n // 4\. Format (Image, Video, Carousel)\\n const type = ad.type || ad.content\_type || ad.format || '';\\n if (type) consolidatedText += \`Format: ${type}\\\\n\`;\\n\\n consolidatedText += '---\\\\n';\\n});\\n\\n// Return ONE item with the consolidated data\\nreturn \[{\\n json: {\\n linkedin\_intelligence: consolidatedText,\\n linkedin\_ads\_count: items.length,\\n company\_name: competitorName // Use the safely retrieved name\\n }\\n}\];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1008, 1664\], "id": "2830b8a7-84eb-473b-8ad4-19587e6ea0ce", "name": "Code in JavaScript2" }, { "parameters": { "modelId": { "\_\_rl": true, "value": "claude-opus-4-5-20251101", "mode": "list", "cachedResultName": "claude-opus-4-5-20251101" }, "messages": { "values": \[ { "content": "=Analyze this competitor data for :\\n\\nGoogle Ads Data: \\nMeta Ads Data: \\n\\nLinkedIn Intelligence: \\n\\nProvide analysis in JSON format:\\n{\\n \\"competitor\_name\\": \\"name\\",\\n \\"ad\_copies\\": \[\\"copy1\\", \\"copy2\\"\],\\n \\"keywords\_identified\\": \[\\"keyword1\\", \\"keyword2\\"\],\\n \\"messaging\_themes\\": \[\\"theme1\\", \\"theme2\\"\],\\n \\"targeting\_insights\\": \\"insights about their target audience\\",\\n \\"competitive\_advantages\\": \[\\"advantage1\\", \\"advantage2\\"\],\\n \\"opportunity\_gaps\\": \[\\"gap1\\", \\"gap2\\"\],\\n \\"recommended\_actions\\": \[\\"action1\\", \\"action2\\"\],\\n \\"threat\_level\\": \\"low/medium/high\\",\\n \\"confidence\_score\\": \\"0-100\\"\\n}\`\\n }\\n \]\\n })\\n}" }, { "content": "You are an expert digital marketing analyst specializing in competitive intelligence. Analyze ad content and extract actionable insights about competitors' strategies, keywords, messaging, and opportunities.", "role": "assistant" } \] }, "options": { "maxTokens": 2000, "temperature": 0.2 } }, "type": "@n8n/n8n-nodes-langchain.anthropic", "typeVersion": 1, "position": \[-560, 1568\], "id": "ca1c7dc3-f5e7-4dad-917c-8c04ad0df235", "name": "🧠 AI Ad Copy & Keywords Analysis" }, { "parameters": { "modelId": { "\_\_rl": true, "value": "claude-opus-4-5-20251101", "mode": "list", "cachedResultName": "claude-opus-4-5-20251101" }, "messages": { "values": \[ { "content": "=" }, { "content": "You are a strategic business analyst. Create executive-level competitive intelligence reports with actionable insights and recommendations.", "role": "assistant" } \] }, "options": { "maxTokens": 3000, "temperature": 0.3 } }, "type": "@n8n/n8n-nodes-langchain.anthropic", "typeVersion": 1, "position": \[464, 1568\], "id": "c9601a52-8a6f-4d06-906c-277da001d2d9", "name": "Message a model" }, { "parameters": { "jsCode": "// Get all ads returned by the TikTok scraper\\nconst items = $input.all();\\n\\n// Safely retrieve the competitor name from the Parse Competitors Data1 node\\nconst competitorName = $node\['Parse Competitors Data1'\].json.competitor\_name || \\"Unknown\\";\\n\\n// If no data came back\\nif (items.length === 0) {\\n return \[{\\n json: {\\n tiktok\_ads\_data: \\"No ads found or scraper returned no data.\\",\\n tiktok\_ads\_count: 0,\\n company\_name: competitorName\\n }\\n }\];\\n}\\n\\nlet consolidatedText = \`Found ${items.length} TikTok ads for ${competitorName}.\\\\n\\\\n\`;\\n\\nitems.forEach((item, index) => {\\n const ad = item.json;\\n\\n consolidatedText += \`Ad ${index + 1}:\\\\n\`;\\n\\n // 1\. Main Text\\n const mainText = ad.text || ad.caption || ad.description || '';\\n if (mainText) consolidatedText += \`Text: ${mainText}\\\\n\`;\\n\\n // 2\. Video URL\\n const videoUrl = ad.video\_url || ad.url || '';\\n if (videoUrl) consolidatedText += \`Video: ${videoUrl}\\\\n\`;\\n\\n // 3\. Hashtags\\n const hashtags = ad.hashtags || ad.tags || \[\];\\n if (hashtags && hashtags.length > 0) consolidatedText += \`Hashtags: ${hashtags.join(', ')}\\\\n\`;\\n\\n consolidatedText += '---\\\\n';\\n});\\n\\n// Return ONE item with the consolidated data\\nreturn \[{\\n json: {\\n tiktok\_ads\_data: consolidatedText,\\n tiktok\_ads\_count: items.length,\\n company\_name: competitorName\\n }\\n}\];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": \[-1008, 1856\], "id": "3d7e61d9-0b44-4d0a-a055-10061569a0cb", "name": "Code in JavaScript3" }, { "parameters": { "operation": "Run actor and get dataset", "actorId": { "\_\_rl": true, "value": "JJghSZmShuco4j9gJ", "mode": "list", "cachedResultName": "Facebook Ads Scraper (apify/facebook-ads-scraper)", "cachedResultUrl": "https://console.apify.com/actors/JJghSZmShuco4j9gJ/input" }, "customBody": "={\\n \\"activeStatus\\": \\"active\\",\\n \\"isDetailsPerAd\\": false,\\n \\"onlyTotal\\": false,\\n \\"resultsLimit\\": 99999,\\n \\"startUrls\\": \[\\n {\\n \\"url\\": \\"https://www.facebook.com/ads/library/?active\_status=all&ad\_type=all&q=\\"\\n }\\n \]\\n}", "timeout": {} }, "type": "@apify/n8n-nodes-apify.apify", "typeVersion": 1, "position": \[-1232, 1472\], "id": "b3d3d604-0a43-4037-b535-cb30c1aff46d", "name": "Facebook Ads Scraper" }, { "parameters": { "operation": "Run actor and get dataset", "actorId": { "\_\_rl": true, "value": "ojJfB9pfBE9aARO3A", "mode": "list", "cachedResultName": "LinkedIn Ads Scraper (ivanvs/linkedin-ads-scraper)", "cachedResultUrl": "https://console.apify.com/actors/ojJfB9pfBE9aARO3A/input" }, "customBody": "={\\n \\"maxRecords\\": 10,\\n \\"urls\\": \[\\n {\\n \\"url\\": \\"https://www.linkedin.com/ad-library/search?accountOwner=\\"\\n }\\n \]\\n}", "timeout": {} }, "type": "@apify/n8n-nodes-apify.apify", "typeVersion": 1, "position": \[-1232, 1664\], "id": "29c51e1b-fb89-4b50-b68a-8101f9694bab", "name": "LinkedIn Ads Scraper" }, { "parameters": { "operation": "Run actor and get dataset", "actorSource": "store", "actorId": { "\_\_rl": true, "value": "AovGexsTSmlalAFzp", "mode": "list", "cachedResultName": "Tiktok Ads Scraper (silva95gustavo/tiktok-ads-scraper)", "cachedResultUrl": "https://console.apify.com/actors/AovGexsTSmlalAFzp/input" }, "customBody": "={\\n \\"skipDetails\\": false,\\n \\"startUrls\\": \[\\n {\\n \\"url\\": \\"https://library.tiktok.com/ads?region=all&start\_time=1735689600000&end\_time=1745967600000&adv\_name=&adv\_biz\_ids=6891503886842987266&query\_type=2&sort\_type=last\_shown\_date,desc\\"\\n }\\n \]\\n}", "timeout": {} }, "type": "@apify/n8n-nodes-apify.apify", "typeVersion": 1, "position": \[-1232, 1856\], "id": "5e14ff9e-c092-4c19-acde-fb8897cefbd1", "name": "TikTok Ads Scraper" } \], "pinData": {}, "connections": { "🔍 AI Competitor Discovery1": { "main": \[\[{"node": "Parse Newline-Delimited JSON1", "type": "main", "index": 0}\]\] }, "Parse Newline-Delimited JSON1": { "main": \[\[{"node": "Parse Competitors Data1", "type": "main", "index": 0}\]\] }, "Parse Competitors Data1": { "main": \[\[ {"node": "Google Ads", "type": "main", "index": 0}, {"node": "LinkedIn Ads Scraper", "type": "main", "index": 0}, {"node": "TikTok Ads Scraper", "type": "main", "index": 0}, {"node": "Facebook Ads Scraper", "type": "main", "index": 0} \]\] }, "Merge All Ad Data1": { "main": \[\[{"node": "🧠 AI Ad Copy & Keywords Analysis", "type": "main", "index": 0}\]\] }, "Parse AI Analysis Results1": { "main": \[\[{"node": "🎯 Competitive Opportunity Scoring1", "type": "main", "index": 0}\]\] }, "🎯 Competitive Opportunity Scoring1": { "main": \[\[{"node": "Aggregate All Results1", "type": "main", "index": 0}\]\] }, "Aggregate All Results1": { "main": \[\[{"node": "Message a model", "type": "main", "index": 0}\]\] }, "On form submission1": { "main": \[\[{"node": "🔍 AI Competitor Discovery1", "type": "main", "index": 0}\]\] }, "Google Ads": { "main": \[\[{"node": "Code in JavaScript", "type": "main", "index": 0}\]\] }, "Code in JavaScript": { "main": \[\[{"node": "Merge All Ad Data1", "type": "main", "index": 0}\]\] }, "Code in JavaScript1": { "main": \[\[{"node": "Merge All Ad Data1", "type": "main", "index": 0}\]\] }, "Code in JavaScript2": { "main": \[\[{"node": "Merge All Ad Data1", "type": "main", "index": 1}\]\] }, "🧠 AI Ad Copy & Keywords Analysis": { "main": \[\[{"node": "Parse AI Analysis Results1", "type": "main", "index": 0}\]\] }, "Message a model": { "main": \[\[{"node": "📧 Send Weekly Intelligence Report1", "type": "main", "index": 0}\]\] }, "Code in JavaScript3": { "main": \[\[{"node": "Merge All Ad Data1", "type": "main", "index": 1}\]\] }, "Facebook Ads Scraper": { "main": \[\[{"node": "Code in JavaScript1", "type": "main", "index": 0}\]\] }, "LinkedIn Ads Scraper": { "main": \[\[{"node": "Code in JavaScript2", "type": "main", "index": 0}\]\] }, "TikTok Ads Scraper": { "main": \[\[{"node": "Code in JavaScript3", "type": "main", "index": 0}\]\] } }, "active": false, "settings": { "executionOrder": "v1" }, "meta": { "templateCredsSetupCompleted": false }, "tags": \[\] }