{
  "id": "CObdaw0Z4MAzAjPn",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Influencer Matchmaking Engine",
  "tags": [],
  "nodes": [
    {
      "id": "a011d233-9fa0-4826-bcfb-ff372a3b6922",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -256
      ],
      "parameters": {
        "width": 980,
        "height": 920,
        "content": "## AI Influencer Matchmaking Engine\n\nThis workflow automatically matches brands with high-converting influencers by analyzing audience insights, engagement patterns, niche alignment, and historical conversion metrics \u2014 then delivers a ranked shortlist with detailed match rationale.\n\n### Who's it for\n\u2022 Brand managers launching influencer campaigns\n\u2022 Marketing agencies managing multiple brand clients\n\u2022 Growth teams running performance-driven influencer programs\n\n### How it works / What it does\n1. Accepts brand requirements via webhook (industry, audience, budget, KPIs)\n2. Pulls influencer data from CRM / database or a Google Sheet roster\n3. AI analyzes niche alignment, audience overlap, and engagement quality\n4. Python scoring engine ranks influencers by conversion probability\n5. Filters are applied based on brand budget and minimum thresholds\n6. AI generates match rationale and performance predictions\n7. Delivers ranked report via email and logs results to Google Sheets\n\n### How to set up\n1. Import this workflow\n2. Configure credentials (Gmail/SendGrid, Google Sheets, OpenAI/Anthropic)\n3. Populate your influencer data source (Google Sheet or CRM webhook)\n4. Activate workflow and POST brand requirements to the webhook URL\n\n### Requirements\n\u2022 Webhook endpoint (or manual trigger for testing)\n\u2022 Google Sheets (influencer roster + results tracker)\n\u2022 OpenAI / Anthropic / Grok API key\n\u2022 SendGrid or Gmail for delivery\n\n### How to customize\n\u2022 Adjust scoring weights in the Python node\n\u2022 Change AI tone/output format in the AI nodes\n\u2022 Add more filter criteria in the Filter node\n\u2022 Extend the Google Sheet schema with additional columns"
      },
      "typeVersion": 1
    },
    {
      "id": "0be70b9e-afdf-4e98-be8f-9e2d3b4fcc40",
      "name": "Sticky Note - Stage 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        -16
      ],
      "parameters": {
        "color": 3,
        "width": 780,
        "height": 500,
        "content": "## 1. Intake & Brand Requirements"
      },
      "typeVersion": 1
    },
    {
      "id": "dd3144bd-8304-4c9b-9fc7-56a36b0b7d49",
      "name": "Sticky Note - Stage 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2256,
        -64
      ],
      "parameters": {
        "color": 3,
        "width": 820,
        "height": 540,
        "content": "## 2. Data Enrichment & Influencer Fetch"
      },
      "typeVersion": 1
    },
    {
      "id": "c541fb77-72f4-4ca1-8ab5-bba6c5e1ffd1",
      "name": "Sticky Note - Stage 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3136,
        -48
      ],
      "parameters": {
        "color": 3,
        "width": 820,
        "height": 628,
        "content": "## 3. AI Scoring & Matching Logic"
      },
      "typeVersion": 1
    },
    {
      "id": "7a9734e9-c640-4673-b8bb-844f84cdbdad",
      "name": "Sticky Note - Stage 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4016,
        -64
      ],
      "parameters": {
        "color": 3,
        "width": 1184,
        "height": 560,
        "content": "## 4. Ranking, Filtering & Output Delivery"
      },
      "typeVersion": 1
    },
    {
      "id": "8eb9a368-db5a-4769-bbe7-31f87fc3d711",
      "name": "Webhook - Brand Match Request",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1568,
        144
      ],
      "parameters": {
        "path": "influencer-match-request",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6685af88-5d20-4534-b932-fce7bbab4283",
      "name": "Schedule - Daily Morning Run",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1568,
        336
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "069668eb-33e9-4840-b55c-d869f650eaa5",
      "name": "Normalize Brand Requirements",
      "type": "n8n-nodes-base.set",
      "position": [
        1824,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "brandName",
              "type": "string",
              "value": "={{ $json.brandName || $json.body?.brandName || 'Unknown Brand' }}"
            },
            {
              "name": "industry",
              "type": "string",
              "value": "={{ $json.industry || $json.body?.industry || 'General' }}"
            },
            {
              "name": "targetAgeRange",
              "type": "string",
              "value": "={{ $json.targetAgeRange || $json.body?.targetAgeRange || '18-34' }}"
            },
            {
              "name": "targetGender",
              "type": "string",
              "value": "={{ $json.targetGender || $json.body?.targetGender || 'All' }}"
            },
            {
              "name": "targetLocations",
              "type": "string",
              "value": "={{ $json.targetLocations || $json.body?.targetLocations || 'US,UK,AU' }}"
            },
            {
              "name": "budgetUSD",
              "type": "number",
              "value": "={{ $json.budgetUSD || $json.body?.budgetUSD || 5000 }}"
            },
            {
              "name": "minEngagementRate",
              "type": "number",
              "value": "={{ $json.minEngagementRate || $json.body?.minEngagementRate || 2.5 }}"
            },
            {
              "name": "minFollowers",
              "type": "number",
              "value": "={{ $json.minFollowers || $json.body?.minFollowers || 10000 }}"
            },
            {
              "name": "campaignGoal",
              "type": "string",
              "value": "={{ $json.campaignGoal || $json.body?.campaignGoal || 'Brand Awareness' }}"
            },
            {
              "name": "preferredPlatforms",
              "type": "string",
              "value": "={{ $json.preferredPlatforms || $json.body?.preferredPlatforms || 'Instagram,TikTok' }}"
            },
            {
              "name": "requestId",
              "type": "string",
              "value": "={{ $json.requestId || 'REQ-' + Date.now().toString() }}"
            },
            {
              "name": "requestedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9cf9efb4-f616-4c0e-84c2-2f804c60d39a",
      "name": "Python - Validate Brand Input",
      "type": "n8n-nodes-base.code",
      "position": [
        2048,
        240
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "python",
        "pythonCode": "# Validate incoming brand requirements\nimport json\n\nitem = _input.item['json']\nerrors = []\n\n# Required field validation\nif not item.get('brandName') or item['brandName'] == 'Unknown Brand':\n    errors.append('brandName is required')\n\nif item.get('budgetUSD', 0) <= 0:\n    errors.append('budgetUSD must be greater than 0')\n\nif item.get('minEngagementRate', 0) < 0 or item.get('minEngagementRate', 0) > 100:\n    errors.append('minEngagementRate must be between 0 and 100')\n\nif item.get('minFollowers', 0) < 0:\n    errors.append('minFollowers cannot be negative')\n\n# Budget tier classification\nbudget = item.get('budgetUSD', 0)\nif budget < 1000:\n    budget_tier = 'nano'\nelif budget < 10000:\n    budget_tier = 'micro'\nelif budget < 50000:\n    budget_tier = 'mid'\nelse:\n    budget_tier = 'macro'\n\nreturn {\n    'json': {\n        **item,\n        'isValid': len(errors) == 0,\n        'validationErrors': errors,\n        'budgetTier': budget_tier\n    }\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "a514d855-f72e-485f-b898-ced6b6cfe695",
      "name": "Gate - Valid Request Only",
      "type": "n8n-nodes-base.filter",
      "position": [
        2272,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.isValid }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "85af47c3-daf2-4246-8884-4810492710d0",
      "name": "Fetch Influencer Roster",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2464,
        144
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_INFLUENCER_ROSTER_SHEET_ID/values/Influencers!A1:Z500?majorDimension=ROWS",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api"
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "58947376-4dfc-4baa-9c41-0a305bb95b77",
      "name": "Fetch Engagement History",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2464,
        336
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_INFLUENCER_ROSTER_SHEET_ID/values/EngagementHistory!A1:Z200?majorDimension=ROWS",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api"
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7957d100-8d8e-479f-8035-0f1492fdbd0d",
      "name": "Python - Build Influencer Profiles",
      "type": "n8n-nodes-base.code",
      "position": [
        2688,
        240
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "# Parse and merge influencer roster + engagement history into structured profiles\nimport json\n\nitems = _input.all()\n\n# Expect two inputs: roster and engagement history\nroster_raw = items[0]['json'].get('values', [])\nengagement_raw = items[1]['json'].get('values', []) if len(items) > 1 else []\n\n# Parse roster (header row first)\nif not roster_raw or len(roster_raw) < 2:\n    return [{'json': {'influencers': [], 'count': 0, 'error': 'Empty influencer roster'}}]\n\nroster_headers = [h.strip().lower().replace(' ', '_') for h in roster_raw[0]]\ninfluencers = []\nfor row in roster_raw[1:]:\n    if not any(row):\n        continue\n    profile = {roster_headers[i]: row[i] if i < len(row) else '' for i in range(len(roster_headers))}\n    profile['follower_count'] = int(profile.get('follower_count', 0) or 0)\n    profile['engagement_rate'] = float(profile.get('engagement_rate', 0) or 0)\n    profile['avg_cpe'] = float(profile.get('avg_cpe', 0) or 0)\n    profile['conversion_rate'] = float(profile.get('conversion_rate', 0) or 0)\n    profile['avg_reach'] = int(profile.get('avg_reach', 0) or 0)\n    influencers.append(profile)\n\n# Parse engagement history and attach to influencers\nif engagement_raw and len(engagement_raw) > 1:\n    eng_headers = [h.strip().lower().replace(' ', '_') for h in engagement_raw[0]]\n    for row in engagement_raw[1:]:\n        if not any(row):\n            continue\n        eng = {eng_headers[i]: row[i] if i < len(row) else '' for i in range(len(eng_headers))}\n        handle = eng.get('handle', '')\n        for inf in influencers:\n            if inf.get('handle', '') == handle:\n                inf['recent_campaigns'] = inf.get('recent_campaigns', []) + [eng]\n\nreturn [{'json': {'influencers': influencers, 'count': len(influencers)}}]"
      },
      "typeVersion": 2
    },
    {
      "id": "31f2fc4a-22ad-43e8-9f33-2cd58e45787e",
      "name": "Merge Brand + Influencer Context",
      "type": "n8n-nodes-base.set",
      "position": [
        2912,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "influencerProfiles",
              "type": "string",
              "value": "={{ JSON.stringify($json.influencers) }}"
            },
            {
              "name": "influencerCount",
              "type": "number",
              "value": "={{ $json.count }}"
            },
            {
              "name": "brandName",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.brandName }}"
            },
            {
              "name": "industry",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.industry }}"
            },
            {
              "name": "targetAgeRange",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.targetAgeRange }}"
            },
            {
              "name": "targetGender",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.targetGender }}"
            },
            {
              "name": "targetLocations",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.targetLocations }}"
            },
            {
              "name": "budgetUSD",
              "type": "number",
              "value": "={{ $('Normalize Brand Requirements').item.json.budgetUSD }}"
            },
            {
              "name": "budgetTier",
              "type": "string",
              "value": "={{ $('Python - Validate Brand Input').item.json.budgetTier }}"
            },
            {
              "name": "minEngagementRate",
              "type": "number",
              "value": "={{ $('Normalize Brand Requirements').item.json.minEngagementRate }}"
            },
            {
              "name": "minFollowers",
              "type": "number",
              "value": "={{ $('Normalize Brand Requirements').item.json.minFollowers }}"
            },
            {
              "name": "campaignGoal",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.campaignGoal }}"
            },
            {
              "name": "preferredPlatforms",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.preferredPlatforms }}"
            },
            {
              "name": "requestId",
              "type": "string",
              "value": "={{ $('Normalize Brand Requirements').item.json.requestId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "da14f61a-eb3c-4c4c-a249-8f9710320671",
      "name": "Python - Score & Rank Influencers",
      "type": "n8n-nodes-base.code",
      "position": [
        3184,
        240
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "python",
        "pythonCode": "# Scoring engine: calculates match score for each influencer against brand requirements\nimport json\n\nitem = _input.item['json']\n\nbrand_industry = item.get('industry', '').lower()\nbrand_age = item.get('targetAgeRange', '18-34')\nbrand_gender = item.get('targetGender', 'all').lower()\nbrand_locations = [l.strip().lower() for l in item.get('targetLocations', 'us').split(',')]\nbrand_budget = float(item.get('budgetUSD', 5000))\nbrand_min_eng = float(item.get('minEngagementRate', 2.5))\nbrand_min_followers = int(item.get('minFollowers', 10000))\nbrand_goal = item.get('campaignGoal', 'Brand Awareness').lower()\nbrand_platforms = [p.strip().lower() for p in item.get('preferredPlatforms', 'instagram').split(',')]\n\ntry:\n    influencers = json.loads(item.get('influencerProfiles', '[]'))\nexcept Exception:\n    influencers = []\n\nscored = []\nfor inf in influencers:\n    score = 0\n    reasons = []\n\n    # --- Hard filters ---\n    eng_rate = float(inf.get('engagement_rate', 0))\n    followers = int(inf.get('follower_count', 0))\n    cost_per_post = float(inf.get('avg_cpe', 9999))\n\n    if eng_rate < brand_min_eng:\n        continue\n    if followers < brand_min_followers:\n        continue\n    if cost_per_post > brand_budget:\n        continue\n\n    # --- Niche alignment (0-30 pts) ---\n    inf_niche = inf.get('niche', '').lower()\n    if brand_industry in inf_niche or inf_niche in brand_industry:\n        score += 30\n        reasons.append('Strong niche match')\n    elif any(kw in inf_niche for kw in brand_industry.split()):\n        score += 15\n        reasons.append('Partial niche overlap')\n\n    # --- Audience demographics (0-25 pts) ---\n    inf_age = inf.get('primary_audience_age', '')\n    if brand_age in inf_age or inf_age in brand_age:\n        score += 15\n        reasons.append('Audience age aligns')\n\n    inf_gender = inf.get('primary_audience_gender', '').lower()\n    if brand_gender == 'all' or brand_gender in inf_gender:\n        score += 10\n        reasons.append('Gender demographic match')\n\n    # --- Location overlap (0-15 pts) ---\n    inf_locations = [l.strip().lower() for l in inf.get('top_locations', '').split(',')]\n    loc_overlap = len(set(brand_locations) & set(inf_locations))\n    if loc_overlap > 0:\n        score += min(15, loc_overlap * 5)\n        reasons.append(f'{loc_overlap} location(s) overlap')\n\n    # --- Engagement quality (0-20 pts) ---\n    if eng_rate >= brand_min_eng * 2:\n        score += 20\n        reasons.append('Exceptional engagement rate')\n    elif eng_rate >= brand_min_eng * 1.5:\n        score += 12\n        reasons.append('Strong engagement rate')\n    else:\n        score += 5\n        reasons.append('Meets engagement threshold')\n\n    # --- Platform fit (0-10 pts) ---\n    inf_platforms = [p.strip().lower() for p in inf.get('platforms', '').split(',')]\n    platform_match = len(set(brand_platforms) & set(inf_platforms))\n    if platform_match > 0:\n        score += min(10, platform_match * 5)\n        reasons.append(f'{platform_match} platform(s) match')\n\n    # --- Conversion history bonus (0-10 pts) ---\n    conv_rate = float(inf.get('conversion_rate', 0))\n    if conv_rate >= 5:\n        score += 10\n        reasons.append('High historical conversion rate')\n    elif conv_rate >= 2:\n        score += 5\n        reasons.append('Moderate conversion history')\n\n    # Estimated conversion probability (rough model)\n    conv_prob = round(min(95, (score / 110) * 100 * (1 + conv_rate / 100)), 1)\n\n    scored.append({\n        **inf,\n        'matchScore': score,\n        'matchReasons': reasons,\n        'conversionProbability': conv_prob\n    })\n\n# Sort by match score descending\nscored.sort(key=lambda x: x['matchScore'], reverse=True)\n\nreturn {\n    'json': {\n        **item,\n        'scoredInfluencers': scored,\n        'totalCandidates': len(scored)\n    }\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "33c27625-5eb9-440a-a77a-01e7476250ab",
      "name": "Wait - Rate Limit Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        3408,
        240
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "beef0e20-9ca3-4769-acbd-6b2ada7b8ee9",
      "name": "AI - Generate Match Rationale",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3632,
        240
      ],
      "parameters": {
        "text": "=You are an expert influencer marketing strategist. Analyze the following brand requirements and top influencer matches, then generate a professional matchmaking report.\n\nBrand: {{ $json.brandName }}\nIndustry: {{ $json.industry }}\nCampaign Goal: {{ $json.campaignGoal }}\nTarget Audience: Age {{ $json.targetAgeRange }}, Gender: {{ $json.targetGender }}, Locations: {{ $json.targetLocations }}\nBudget: ${{ $json.budgetUSD }} USD\nPreferred Platforms: {{ $json.preferredPlatforms }}\n\nTop Matched Influencers (scored and ranked):\n{{ JSON.stringify($json.scoredInfluencers?.slice(0, 10)) }}\n\nYour tasks:\n1. Write a concise executive summary (3-4 sentences) about the overall match quality.\n2. For the top 5 influencers, write a 2-3 sentence rationale explaining WHY they are a strong fit for this brand.\n3. Highlight any risks or caveats (audience authenticity, niche drift, budget fit).\n4. Suggest one creative campaign concept that aligns with the brand goal.\n\nFormat your response as structured JSON with keys: executiveSummary, topMatches (array with handle, rationale, conversionProbability), risks, campaignConcept.",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "660f5411-4777-4ba3-868f-5dfcdd403bef",
      "name": "JS - Assemble Final Report",
      "type": "n8n-nodes-base.code",
      "position": [
        4080,
        240
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Parse AI response (may be wrapped in markdown)\nlet aiOutput = item.output || item.response || '{}';\ntry {\n  // Strip markdown code fences if present\n  aiOutput = aiOutput.replace(/```json|```/g, '').trim();\n  aiOutput = JSON.parse(aiOutput);\n} catch (e) {\n  aiOutput = { rawResponse: item.output || '', parseError: true };\n}\n\n// Build final ranked shortlist (top 10)\nconst topInfluencers = (item.scoredInfluencers || []).slice(0, 10).map((inf, idx) => ({\n  rank: idx + 1,\n  handle: inf.handle || inf.name || 'Unknown',\n  name: inf.name || inf.handle || 'Unknown',\n  niche: inf.niche || 'General',\n  platform: inf.platforms || 'Instagram',\n  followerCount: inf.follower_count || 0,\n  engagementRate: inf.engagement_rate || 0,\n  conversionRate: inf.conversion_rate || 0,\n  matchScore: inf.matchScore || 0,\n  conversionProbability: inf.conversionProbability || 0,\n  matchReasons: (inf.matchReasons || []).join('; '),\n  estimatedCostPerPost: inf.avg_cpe || 0,\n  topLocations: inf.top_locations || '',\n  audienceAge: inf.primary_audience_age || '',\n  audienceGender: inf.primary_audience_gender || ''\n}));\n\nreturn {\n  json: {\n    requestId: item.requestId,\n    brandName: item.brandName,\n    industry: item.industry,\n    campaignGoal: item.campaignGoal,\n    budgetUSD: item.budgetUSD,\n    totalCandidatesEvaluated: item.totalCandidates || 0,\n    totalMatched: topInfluencers.length,\n    rankedShortlist: topInfluencers,\n    executiveSummary: aiOutput.executiveSummary || '',\n    aiTopMatches: aiOutput.topMatches || [],\n    risks: aiOutput.risks || '',\n    campaignConcept: aiOutput.campaignConcept || '',\n    generatedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f020b84c-3568-42ab-9fc2-8a124650993f",
      "name": "Filter - Matches Found",
      "type": "n8n-nodes-base.filter",
      "position": [
        4304,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.totalMatched }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "409e9fd5-49f0-4f72-9bdf-dfdf21f8c087",
      "name": "Send Match Report Email",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4752,
        144
      ],
      "parameters": {
        "url": "https://api.sendgrid.com/v3/mail/send",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"personalizations\": [{\n    \"to\": [{\"email\": \"brand-team@yourdomain.com\"}],\n    \"subject\": \"Influencer Match Report: {{ $json.brandName }} \u2014 {{ $json.totalMatched }} Matches Found\"\n  }],\n  \"from\": {\"email\": \"matchmaker@yourdomain.com\", \"name\": \"AI Matchmaking Engine\"},\n  \"content\": [{\n    \"type\": \"text/html\",\n    \"value\": \"<h2>Influencer Match Report for {{ $json.brandName }}</h2><p><strong>Campaign Goal:</strong> {{ $json.campaignGoal }}</p><p><strong>Budget:</strong> ${{ $json.budgetUSD }}</p><p><strong>Candidates Evaluated:</strong> {{ $json.totalCandidatesEvaluated }}</p><p><strong>Top Matches Found:</strong> {{ $json.totalMatched }}</p><h3>Executive Summary</h3><p>{{ $json.executiveSummary }}</p><h3>Campaign Concept</h3><p>{{ $json.campaignConcept }}</p><h3>Top Ranked Influencers</h3><pre>{{ JSON.stringify($json.rankedShortlist, null, 2) }}</pre><h3>Risks & Caveats</h3><p>{{ $json.risks }}</p><p><em>Generated: {{ $json.generatedAt }}</em></p>\"\n  }]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "2963ca68-2745-49e2-b4ec-9bf337a5dac6",
      "name": "Log Results to Google Sheet",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4752,
        336
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_RESULTS_SHEET_ID/values/MatchResults!A1:append?valueInputOption=USER_ENTERED",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"values\": [\n    [\n      \"{{ $json.generatedAt }}\",\n      \"{{ $json.requestId }}\",\n      \"{{ $json.brandName }}\",\n      \"{{ $json.industry }}\",\n      \"{{ $json.campaignGoal }}\",\n      {{ $json.budgetUSD }},\n      {{ $json.totalCandidatesEvaluated }},\n      {{ $json.totalMatched }},\n      \"{{ $json.rankedShortlist[0]?.handle || 'N/A' }}\",\n      {{ $json.rankedShortlist[0]?.matchScore || 0 }},\n      {{ $json.rankedShortlist[0]?.conversionProbability || 0 }},\n      \"{{ $json.executiveSummary?.replace(/\"/g, \"'\") }}\"\n    ]\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api"
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "178d76ff-a727-41b8-9d8e-01f8911c2204",
      "name": "Webhook Response - Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        4976,
        240
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, requestId: $json.requestId, totalMatched: $json.totalMatched, topMatch: $json.rankedShortlist[0]?.handle || null, message: 'Match report generated and delivered.' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "7acfb38e-b982-468b-ade8-2b6b31566fbd",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        3504,
        448
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b471c741-9b51-4fb7-a459-a10d3d7b4a59",
  "connections": {
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Generate Match Rationale",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Filter - Matches Found": {
      "main": [
        [
          {
            "node": "Send Match Report Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Results to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Influencer Roster": {
      "main": [
        [
          {
            "node": "Python - Build Influencer Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Match Report Email": {
      "main": [
        [
          {
            "node": "Webhook Response - Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Engagement History": {
      "main": [
        [
          {
            "node": "Python - Build Influencer Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Rate Limit Buffer": {
      "main": [
        [
          {
            "node": "AI - Generate Match Rationale",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gate - Valid Request Only": {
      "main": [
        [
          {
            "node": "Fetch Influencer Roster",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Engagement History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Assemble Final Report": {
      "main": [
        [
          {
            "node": "Filter - Matches Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Results to Google Sheet": {
      "main": [
        [
          {
            "node": "Webhook Response - Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Brand Requirements": {
      "main": [
        [
          {
            "node": "Python - Validate Brand Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule - Daily Morning Run": {
      "main": [
        [
          {
            "node": "Normalize Brand Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Generate Match Rationale": {
      "main": [
        [
          {
            "node": "JS - Assemble Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Validate Brand Input": {
      "main": [
        [
          {
            "node": "Gate - Valid Request Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Brand Match Request": {
      "main": [
        [
          {
            "node": "Normalize Brand Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Brand + Influencer Context": {
      "main": [
        [
          {
            "node": "Python - Score & Rank Influencers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Score & Rank Influencers": {
      "main": [
        [
          {
            "node": "Wait - Rate Limit Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Build Influencer Profiles": {
      "main": [
        [
          {
            "node": "Merge Brand + Influencer Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}