{
  "name": "Influencer Enrichment + Scoring Pipeline",
  "nodes": [
    {
      "parameters": { "httpMethod": "POST", "path": "enrich-influencers", "responseMode": "responseNode", "options": {} },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 300],
      "id": "webhook-trigger",
      "name": "Webhook Trigger",
      "notes": "POST with { \"listId\": \"YOUR_HUBSPOT_LIST_ID\" } or trigger manually. Can also use Schedule Trigger for batch runs."
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.hubapi.com/contacts/v1/lists/{{ $json.body.listId || 'YOUR_LIST_ID' }}/contacts/all",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotOAuth2Api",
        "sendQuery": true,
        "queryParameters": { "parameters": [{ "name": "count", "value": "100" }, { "name": "property", "value": "email" }, { "name": "property", "value": "firstname" }, { "name": "property", "value": "lastname" }, { "name": "property", "value": "hs_linkedin_url" }] },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [240, 300],
      "id": "hubspot-export",
      "name": "HubSpot — Export Grad List",
      "credentials": { "hubspotOAuth2Api": { "id": "YOUR_CREDENTIAL_ID", "name": "HubSpot" } }
    },
    {
      "parameters": {
        "jsCode": "// Extract contacts into flat array\nconst data = $input.first().json;\nconst contacts = (data.contacts || []).map(c => {\n  const props = c.properties || {};\n  return {\n    email: props.email?.value || '',\n    firstName: props.firstname?.value || '',\n    lastName: props.lastname?.value || '',\n    linkedinUrl: props.hs_linkedin_url?.value || '',\n    hubspotId: c.vid\n  };\n}).filter(c => c.linkedinUrl); // Only process contacts with LinkedIn URLs\n\nreturn contacts.map(c => ({ json: c }));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [480, 300],
      "id": "parse-contacts",
      "name": "Parse Contacts"
    },
    {
      "parameters": { "batchSize": 5, "options": { "reset": false } },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [700, 300],
      "id": "batch-contacts",
      "name": "Batch (5 at a time)"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/acts/YOUR_ACTOR_ID/runs?token=YOUR_APIFY_TOKEN",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"profileUrls\": [\"{{ $json.linkedinUrl }}\"]\n}",
        "options": { "timeout": 60000 }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [920, 300],
      "id": "apify-linkedin",
      "name": "Apify — LinkedIn Enrichment",
      "notes": "Uses the harvestapi LinkedIn actor. Replace YOUR_ACTOR_ID and YOUR_APIFY_TOKEN."
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/actor-runs/{{ $json.data.id }}/dataset/items?token=YOUR_APIFY_TOKEN",
        "options": { "timeout": 30000 }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1140, 300],
      "id": "fetch-linkedin-results",
      "name": "Fetch LinkedIn Results"
    },
    {
      "parameters": {
        "jsCode": "// Score & classify LinkedIn profiles\nconst KEYWORDS = [\n  'coach', 'coaching', 'speaker', 'author', 'consultant',\n  'trainer', 'facilitator', 'mentor', 'podcast', 'leadership',\n  'wellness', 'mindset', 'executive', 'career', 'transformation'\n];\n\nconst items = $input.all();\nconst contact = $('Batch (5 at a time)').first().json;\nconst output = [];\n\nfor (const item of items) {\n  const d = item.json;\n  \n  // Extract fields with null safety\n  const connections = d.connectionsCount || 0;\n  const followers = d.followerCount || 0;\n  const verified = d.verified === true;\n  const hasAbout = !!d.about;\n  const hasSkills = !!(d.topSkills && d.topSkills.length > 0);\n  const currentTitle = (d.currentPosition?.[0]?.title || d.headline || '').toLowerCase();\n  const currentCompany = d.currentPosition?.[0]?.companyName || '';\n  const country = d.location?.parsed?.countryCode || '';\n  \n  // Extract Instagram handle from bio/website\n  let instagramHandle = '';\n  const aboutText = d.about || '';\n  const igMatch = aboutText.match(/@([a-zA-Z0-9_.]+)/)\n    || aboutText.match(/instagram\\.com\\/([a-zA-Z0-9_.]+)/i);\n  if (igMatch) instagramHandle = igMatch[1];\n  \n  // === SCORING ===\n  let score = 0;\n  \n  // Reach (0-40 pts)\n  if (followers >= 5000) score += 40;\n  else if (followers >= 2000) score += 25;\n  else if (followers >= 500) score += 10;\n  \n  // Connections (0-20 pts)\n  if (connections >= 3000) score += 20;\n  else if (connections >= 1000) score += 10;\n  \n  // Profile completeness (0-20 pts)\n  if (verified) score += 10;\n  if (hasAbout) score += 5;\n  if (hasSkills) score += 5;\n  \n  // US-based bonus (0-10 pts)\n  if (country === 'US') score += 10;\n  \n  // Coach/relevant title (0-10 pts)\n  if (KEYWORDS.some(k => currentTitle.includes(k))) score += 10;\n  \n  // Tier assignment\n  let tier = 'C';\n  if (score >= 70) tier = 'A';\n  else if (score >= 45) tier = 'B';\n  \n  output.push({\n    json: {\n      // Original contact\n      email: contact.email,\n      firstName: contact.firstName,\n      lastName: contact.lastName,\n      hubspotId: contact.hubspotId,\n      \n      // Enriched data\n      linkedinUrl: contact.linkedinUrl,\n      headline: d.headline || '',\n      currentTitle: d.currentPosition?.[0]?.title || '',\n      currentCompany,\n      connections,\n      followers,\n      verified,\n      country,\n      instagramHandle,\n      \n      // Scoring\n      influencer_score: score,\n      influencer_tier: tier\n    }\n  });\n}\n\nreturn output;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1360, 300],
      "id": "score-classify",
      "name": "Score & Classify (A/B/C)"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hubspotId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"properties\": {\n    \"influencer_score\": \"{{ $json.influencer_score }}\",\n    \"influencer_tier\": \"{{ $json.influencer_tier }}\",\n    \"linkedin_followers\": \"{{ $json.followers }}\",\n    \"linkedin_connections\": \"{{ $json.connections }}\",\n    \"current_title\": \"{{ $json.currentTitle }}\",\n    \"current_company\": \"{{ $json.currentCompany }}\",\n    \"instagram_handle\": \"{{ $json.instagramHandle }}\"\n  }\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1580, 300],
      "id": "update-hubspot",
      "name": "Update HubSpot Contact",
      "credentials": { "hubspotOAuth2Api": { "id": "YOUR_CREDENTIAL_ID", "name": "HubSpot" } }
    },
    {
      "parameters": {
        "operation": "create",
        "baseUrl": "https://YOUR_NOCODB_HOST",
        "table": "YOUR_TABLE_ID",
        "fieldsUi": {
          "fieldValues": [
            { "fieldName": "Name", "fieldValue": "={{ $json.firstName }} {{ $json.lastName }}" },
            { "fieldName": "Email", "fieldValue": "={{ $json.email }}" },
            { "fieldName": "LinkedIn", "fieldValue": "={{ $json.linkedinUrl }}" },
            { "fieldName": "Title", "fieldValue": "={{ $json.currentTitle }}" },
            { "fieldName": "Company", "fieldValue": "={{ $json.currentCompany }}" },
            { "fieldName": "Followers", "fieldValue": "={{ $json.followers }}" },
            { "fieldName": "Connections", "fieldValue": "={{ $json.connections }}" },
            { "fieldName": "Score", "fieldValue": "={{ $json.influencer_score }}" },
            { "fieldName": "Tier", "fieldValue": "={{ $json.influencer_tier }}" },
            { "fieldName": "Instagram", "fieldValue": "={{ $json.instagramHandle }}" }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.nocoDb",
      "typeVersion": 3,
      "position": [1580, 500],
      "id": "save-nocodb",
      "name": "Log to NocoDB Dashboard",
      "credentials": { "nocoDbApiToken": { "id": "YOUR_CREDENTIAL_ID", "name": "NocoDB" } }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"processed\": $('Parse Contacts').all().length, \"tierA\": $('Score & Classify (A/B/C)').all().filter(i => i.json.influencer_tier === 'A').length, \"tierB\": $('Score & Classify (A/B/C)').all().filter(i => i.json.influencer_tier === 'B').length, \"tierC\": $('Score & Classify (A/B/C)').all().filter(i => i.json.influencer_tier === 'C').length } }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [1800, 300],
      "id": "respond-webhook",
      "name": "Return Results Summary"
    }
  ],
  "connections": {
    "Webhook Trigger": { "main": [[{ "node": "HubSpot — Export Grad List", "type": "main", "index": 0 }]] },
    "HubSpot — Export Grad List": { "main": [[{ "node": "Parse Contacts", "type": "main", "index": 0 }]] },
    "Parse Contacts": { "main": [[{ "node": "Batch (5 at a time)", "type": "main", "index": 0 }]] },
    "Batch (5 at a time)": { "main": [[{ "node": "Apify — LinkedIn Enrichment", "type": "main", "index": 0 }]] },
    "Apify — LinkedIn Enrichment": { "main": [[{ "node": "Fetch LinkedIn Results", "type": "main", "index": 0 }]] },
    "Fetch LinkedIn Results": { "main": [[{ "node": "Score & Classify (A/B/C)", "type": "main", "index": 0 }]] },
    "Score & Classify (A/B/C)": { "main": [[{ "node": "Update HubSpot Contact", "type": "main", "index": 0 }, { "node": "Log to NocoDB Dashboard", "type": "main", "index": 0 }]] },
    "Update HubSpot Contact": { "main": [[{ "node": "Return Results Summary", "type": "main", "index": 0 }]] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "meta": {
    "notes": "SETUP: 1) Create custom HubSpot contact properties: influencer_score (number), influencer_tier (single-line text), linkedin_followers (number), linkedin_connections (number), current_title (single-line text), current_company (single-line text), instagram_handle (single-line text). 2) Set up Apify account with harvestapi LinkedIn actor. 3) Create NocoDB table for dashboard view. 4) Add all credentials. 5) Replace YOUR_LIST_ID with your HubSpot grad list. 6) Run with small batch first (10 contacts) to validate. 7) Apify has rate limits — batch size of 5 with delays is recommended.",
    "author": "Edward Chalupa — echalupa.com",
    "article": "echalupa.com/blog/influencer-scoring-pipeline"
  }
}
