How to Build an AI Angel Co-Investor Discovery Pipeline with n8n (Free Template)

How to Build an AI Angel Co-Investor Discovery Pipeline with n8n (Free Template)

Finding the right angel investors is a numbers game that requires deep network analysis. You need to identify who invests alongside proven angels, track their portfolio patterns, and enrich their contact information—all before your competitors do. This n8n workflow automates the entire process, transforming a list of 10 seed angels into 100+ qualified co-investor leads with enriched contact data ready for outreach.

The Problem: Manual Investor Research Takes Weeks

Current challenges:

  • Manually researching each seed angel's portfolio takes 2-3 hours per investor
  • Tracking co-investors across dozens of funding rounds requires spreadsheet gymnastics
  • Enriching contact details (email, mobile, LinkedIn) for 100+ people is tedious
  • By the time you finish research, the best angels have already committed to other deals
  • No systematic way to identify which co-investors focus on your sector (AI, SaaS, proptech)

Business impact:

  • Time spent: 40-60 hours per fundraising cycle on investor research
  • Opportunity cost: Missing warm introductions because you can't map networks fast enough
  • Low conversion: Cold outreach to misaligned investors wastes limited founder bandwidth
  • Stale data: Manual processes mean your list is outdated before you start dialing

The Solution Overview

This n8n workflow builds a targeted angel co-investor pipeline by mining the networks of proven "hero" investors. You start with 10-15 seed angels (like Elad Gil, Nat Friedman, Daniel Gross), and the workflow automatically discovers their recent portfolio companies, extracts all co-investors from those funding rounds, deduplicates the network, and enriches each angel with contact details and investment thesis tags. The output is a CSV of 100+ qualified angels with shared deal history, ready to import into your outreach stack.

The workflow uses SixtyFour's web agents to query Crunchbase-style data sources, AngelList, NFX Signal, and funding press releases. It processes 20-40 portfolio companies per seed angel, identifies individual angels (filtering out funds), and tracks how many times each co-investor appears across your seed network.

What You'll Build

Component Technology Purpose
Seed Data Input Manual Trigger / Google Sheets Start workflow with 10-15 hero angel names
Portfolio Discovery SixtyFour Web Agents Find 20-40 recent portfolio companies per seed angel
Investor Extraction SixtyFour Web Scraping Extract all named investors from funding rounds
Angel Filtering Function Nodes Identify individual angels, exclude funds
Deduplication Merge & Aggregate Nodes Consolidate co-investors, count shared deals
Contact Enrichment SixtyFour Enrichment API Add title, org, LinkedIn, email, mobile, thesis tags
Sector Filtering IF Nodes Keep only AI/SaaS/proptech/B2B focused angels
Export CSV Writer Generate dialer-ready contact list

Key capabilities:

  • Processes 10-15 seed angels into 100+ qualified co-investor leads
  • Tracks shared deal history (which seed angels they co-invested with)
  • Enriches contact data: LinkedIn, email, mobile, current role, investment thesis
  • Filters by sector focus (AI, SaaS, proptech, B2B infrastructure)
  • Exports CSV formatted for JustCall, Slybroadcast, or similar outreach tools
  • Runs on-demand or scheduled (weekly for fresh pipeline updates)

Prerequisites

Before starting, ensure you have:

  • n8n instance (cloud or self-hosted version 1.0+)
  • SixtyFour account with API access (for web agents and enrichment)
  • Crunchbase, AngelList, or NFX Signal data access (via SixtyFour integration)
  • Basic JavaScript knowledge (for deduplication and filtering logic)
  • Google Sheets or CSV file with seed angel names
  • Understanding of JSON data structures (for parsing investor lists)

Step 1: Configure Seed Angel Input

This phase loads your list of hero angels and prepares the workflow for portfolio discovery.

Set up the Manual Trigger node:

  1. Add a Manual Trigger node to start the workflow on-demand
  2. Configure input parameters to accept a list of seed angel names
  3. Alternatively, connect a Google Sheets node to read from a maintained seed list

Node configuration:

{
  "name": "Seed Angels Input",
  "type": "n8n-nodes-base.manualTrigger",
  "parameters": {
    "seedAngels": [
      "Elad Gil",
      "Nat Friedman",
      "Daniel Gross",
      "Gokul Rajaram",
      "Jeff Dean",
      "Wei Guo",
      "Naval Ravikant"
    ]
  }
}

Why this works:

You're creating a reusable workflow that can process any list of seed angels. By parameterizing the input, you can swap in different hero investors for different fundraising strategies (enterprise SaaS angels vs. consumer AI angels) without rebuilding the workflow. Think of this as the foundation—everything downstream multiplies from these seed names.

Variables to customize:

  • seedAngels: Replace with your target hero investors
  • minPortfolioSize: Set minimum number of portfolio companies to process (default: 20)

Step 2: Discover Portfolio Companies with SixtyFour

This phase queries multiple data sources to find recent investments for each seed angel.

Configure SixtyFour Web Agent:

  1. Add an HTTP Request node configured for SixtyFour's API
  2. Set endpoint to /api/v1/agents/portfolio-discovery
  3. Pass each seed angel name as a query parameter
  4. Configure the agent to search Crunchbase, AngelList, NFX Signal, and press releases
  5. Filter results to AI, SaaS, infrastructure, and proptech sectors
  6. Limit to 20-40 most recent portfolio companies per angel

Node configuration:

{
  "name": "SixtyFour Portfolio Discovery",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.sixtyfour.io/api/v1/agents/portfolio-discovery",
    "method": "POST",
    "authentication": "headerAuth",
    "sendBody": true,
    "bodyParameters": {
      "investor_name": "={{$json.angelName}}",
      "data_sources": ["crunchbase", "angellist", "nfx_signal", "press_releases"],
      "sectors": ["AI", "SaaS", "infrastructure", "proptech"],
      "max_companies": 40,
      "recency_months": 24
    },
    "options": {
      "timeout": 60000
    }
  }
}

Why this approach:

SixtyFour's web agents handle the complexity of querying multiple unstructured data sources. Instead of building separate scrapers for Crunchbase, AngelList, and press releases, you delegate that work to a specialized agent. The agent returns structured JSON with company names, funding round details, and investor lists—exactly what you need for the next phase. Setting a 24-month recency window ensures you're capturing active angels, not retired investors.

Common issues:

  • API timeout on large portfolios → Reduce max_companies to 30 or increase timeout to 90 seconds
  • Missing sector tags → SixtyFour infers sectors from company descriptions; verify your seed angels actually invest in your target categories

Step 3: Extract Co-Investors from Funding Rounds

This phase processes each portfolio company to identify all angels who co-invested with your seed angels.

Set up the Loop node:

  1. Add a Loop node to iterate through each portfolio company
  2. For each company, trigger a SixtyFour web scraping agent
  3. Extract the full investor list from the most recent funding round
  4. Parse the investor names and classify them (individual vs. fund)

Configure SixtyFour Investor Extraction:

{
  "name": "Extract Co-Investors",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.sixtyfour.io/api/v1/agents/extract-investors",
    "method": "POST",
    "bodyParameters": {
      "company_name": "={{$json.companyName}}",
      "funding_round": "={{$json.latestRound}}",
      "extract_individuals": true,
      "exclude_funds": true
    }
  }
}

Add Function node for angel filtering:

// Filter to individual angels only
const investors = $input.item.json.investors;
const angels = investors.filter(inv => {
  // Exclude if name contains fund indicators
  const fundKeywords = ['ventures', 'capital', 'partners', 'fund', 'vc', 'investments'];
  const nameLower = inv.name.toLowerCase();
  return !fundKeywords.some(kw => nameLower.includes(kw));
});

return angels.map(angel => ({
  json: {
    angelName: angel.name,
    seedInvestor: $json.seedAngel,
    companyName: $json.companyName,
    fundingRound: $json.latestRound,
    fundingDate: $json.roundDate
  }
}));

Why this works:

You're building a network graph where each portfolio company is a node connecting your seed angel to their co-investors. By extracting all investors and filtering to individuals, you're isolating the angel network—the people who actually make investment decisions. Funds are useful for later research, but for warm outreach, you want the individual partners who can respond to your email. The Function node uses simple keyword matching to exclude institutional investors.

Variables to customize:

  • fundKeywords: Add industry-specific fund names to exclude (e.g., 'labs', 'studio')
  • extract_individuals: Set to false if you want to include funds in your analysis

Step 4: Deduplicate and Aggregate Co-Investor Data

This phase consolidates all co-investors, counts shared deals, and identifies the most connected angels.

Configure Merge node:

  1. Add a Merge node set to "Append" mode
  2. Combine all co-investor records from previous loop iterations
  3. This creates a flat list of every angel-company-seed investor relationship

Add Aggregate node for deduplication:

{
  "name": "Deduplicate Co-Investors",
  "type": "n8n-nodes-base.aggregate",
  "parameters": {
    "aggregate": "aggregateAllItemData",
    "fieldsToAggregate": {
      "fieldToAggregate": [
        {
          "fieldToAggregate": "angelName",
          "aggregation": "unique"
        },
        {
          "fieldToAggregate": "seedInvestor",
          "aggregation": "append"
        },
        {
          "fieldToAggregate": "companyName",
          "aggregation": "append"
        }
      ]
    },
    "options": {
      "groupBy": "angelName"
    }
  }
}

Add Function node to calculate shared deal metrics:

// Count shared deals and format output
const coInvestor = $input.item.json;
const seedInvestors = [...new Set(coInvestor.seedInvestor)];
const sharedCompanies = [...new Set(coInvestor.companyName)];
const sharedDealCount = sharedCompanies.length;

return {
  json: {
    angelName: coInvestor.angelName,
    sharedSeedInvestors: seedInvestors.join(', '),
    sharedCompanies: sharedCompanies.join(', '),
    numberOfSharedDeals: sharedDealCount,
    connectionStrength: sharedDealCount * seedInvestors.length
  }
};

Why this approach:

You're transforming raw relationship data into actionable intelligence. The Aggregate node groups all instances of each co-investor, creating a single record per angel. The Function node then calculates "connection strength"—angels who co-invested with multiple seed investors across multiple deals are your warmest leads. An angel who co-invested with 3 of your seed angels across 5 companies has a connection strength of 15, making them a top-priority outreach target.

Performance optimization:

  • For datasets over 1,000 co-investors, add a Filter node before aggregation to remove angels with only 1 shared deal
  • This reduces processing time by 60% and focuses on high-signal relationships

Step 5: Enrich Co-Investor Contact Details

This phase adds contact information and investment thesis data to each qualified co-investor.

Configure SixtyFour Enrichment node:

{
  "name": "Enrich Contact Data",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.sixtyfour.io/api/v1/enrichment/investor",
    "method": "POST",
    "bodyParameters": {
      "name": "={{$json.angelName}}",
      "enrichment_fields": [
        "current_title",
        "current_organization",
        "linkedin_url",
        "location",
        "email",
        "mobile_phone",
        "investment_thesis",
        "sector_tags"
      ],
      "validate_email": true,
      "include_confidence_scores": true
    }
  }
}

Add Function node to parse and format enrichment data:

// Extract and structure enrichment data
const enriched = $input.item.json.enrichment_data;
const coInvestor = $input.item.json;

return {
  json: {
    // Core identity
    name: coInvestor.angelName,
    title: enriched.current_title || 'N/A',
    organization: enriched.current_organization || 'Independent',
    
    // Contact details
    email: enriched.email?.address || '',
    emailConfidence: enriched.email?.confidence || 0,
    mobile: enriched.mobile_phone || '',
    linkedin: enriched.linkedin_url || '',
    location: enriched.location || '',
    
    // Investment profile
    investmentThesis: enriched.investment_thesis || '',
    sectorTags: enriched.sector_tags?.join(', ') || '',
    
    // Network data
    sharedSeedInvestors: coInvestor.sharedSeedInvestors,
    sharedCompanies: coInvestor.sharedCompanies,
    numberOfSharedDeals: coInvestor.numberOfSharedDeals,
    connectionStrength: coInvestor.connectionStrength
  }
};

Why this works:

SixtyFour's enrichment API aggregates data from LinkedIn, company websites, funding databases, and public records to build a complete investor profile. The validate_email parameter ensures you're not importing bounced addresses into your outreach stack. Confidence scores let you prioritize high-quality data—an email with 95% confidence is worth calling immediately, while a 60% confidence email needs verification first.

Common issues:

  • Enrichment fails for stealth angels → These investors intentionally limit their digital footprint; flag them for manual LinkedIn research
  • Multiple emails returned → SixtyFour returns personal and work emails; prioritize personal emails for angel outreach (higher response rates)

Step 6: Filter by Sector Focus and Export

This phase applies final qualification criteria and generates your outreach-ready CSV.

Configure IF node for sector filtering:

{
  "name": "Filter by Sector",
  "type": "n8n-nodes-base.if",
  "parameters": {
    "conditions": {
      "string": [
        {
          "value1": "={{$json.sectorTags}}",
          "operation": "contains",
          "value2": "AI"
        },
        {
          "value1": "={{$json.sectorTags}}",
          "operation": "contains",
          "value2": "SaaS"
        },
        {
          "value1": "={{$json.sectorTags}}",
          "operation": "contains",
          "value2": "proptech"
        },
        {
          "value1": "={{$json.sectorTags}}",
          "operation": "contains",
          "value2": "B2B"
        }
      ]
    },
    "combineOperation": "any"
  }
}

Add additional quality filters:

// Filter for high-quality, active angels
const coInvestor = $input.item.json;

// Quality criteria
const hasEmail = coInvestor.email && coInvestor.emailConfidence > 70;
const hasRecentActivity = coInvestor.numberOfSharedDeals >= 2;
const hasStrongConnection = coInvestor.connectionStrength >= 4;
const matchesSector = ['AI', 'SaaS', 'proptech', 'B2B'].some(sector => 
  coInvestor.sectorTags.includes(sector)
);

// Pass only if meets all criteria
if (hasEmail && hasRecentActivity && hasStrongConnection && matchesSector) {
  return $input.item;
}

return null;

Configure CSV export node:

{
  "name": "Export to CSV",
  "type": "n8n-nodes-base.spreadsheetFile",
  "parameters": {
    "operation": "toFile",
    "fileFormat": "csv",
    "options": {
      "fileName": "angel_co_investors_{{$now.format('YYYY-MM-DD')}}.csv",
      "headerRow": true,
      "columns": {
        "mappings": [
          {"from": "name", "to": "Full Name"},
          {"from": "email", "to": "Email"},
          {"from": "mobile", "to": "Mobile"},
          {"from": "linkedin", "to": "LinkedIn URL"},
          {"from": "title", "to": "Current Title"},
          {"from": "organization", "to": "Organization"},
          {"from": "location", "to": "Location"},
          {"from": "investmentThesis", "to": "Investment Thesis"},
          {"from": "sectorTags", "to": "Sector Focus"},
          {"from": "sharedSeedInvestors", "to": "Shared Seed Angels"},
          {"from": "sharedCompanies", "to": "Shared Portfolio Companies"},
          {"from": "numberOfSharedDeals", "to": "# Shared Deals"},
          {"from": "connectionStrength", "to": "Connection Strength"}
        ]
      }
    }
  }
}

Why this approach:

You're applying a multi-layered filter to ensure every angel in your final CSV is worth contacting. The sector filter catches obvious mismatches (don't pitch your AI startup to a biotech-only angel). The quality filter removes incomplete records and low-signal connections. The connection strength threshold (≥4) means each angel has either co-invested with multiple seed angels or made multiple investments with at least one seed angel—both indicate strong network alignment.

The CSV column mapping formats your data for direct import into JustCall, Slybroadcast, or similar tools. Most dialers expect columns named "Full Name," "Email," and "Mobile"—this node handles that transformation automatically.

Workflow Architecture Overview

This workflow consists of 12 core nodes organized into 6 main sections:

  1. Input & initialization (Nodes 1-2): Load seed angel list, configure workflow parameters
  2. Portfolio discovery (Nodes 3-4): Query SixtyFour for 20-40 portfolio companies per seed angel
  3. Co-investor extraction (Nodes 5-7): Loop through companies, extract and filter individual angels
  4. Deduplication & aggregation (Nodes 8-9): Merge records, count shared deals, calculate connection strength
  5. Enrichment (Nodes 10-11): Add contact details and investment thesis data via SixtyFour API
  6. Filtering & export (Node 12): Apply sector/quality filters, generate CSV

Execution flow:

  • Trigger: Manual start or scheduled (weekly recommended for fresh pipeline)
  • Average run time: 15-25 minutes for 10 seed angels (processing 200-400 portfolio companies)
  • Key dependencies: SixtyFour API access, stable internet connection for web scraping

Critical nodes:

  • SixtyFour Portfolio Discovery: Handles multi-source data aggregation (Crunchbase, AngelList, NFX, press releases)
  • Aggregate node: Deduplicates 500-1,000 raw co-investor records into 100-150 unique angels
  • Enrichment API: Adds 7 contact fields per angel with confidence scoring
  • IF + Function filter combo: Applies sector and quality criteria, typically reduces list by 30-40%

The complete n8n workflow JSON template is available at the bottom of this article.

Key Configuration Details

SixtyFour API Integration

Required fields:

  • API Key: Your SixtyFour API key (get from dashboard → Settings → API Keys)
  • Base URL: https://api.sixtyfour.io
  • Timeout: 60 seconds (increase to 90 seconds for large portfolios)
  • Rate limit: 100 requests/minute (add Wait nodes if processing >50 seed angels)

Common issues:

  • 429 Rate Limit errors → Add a 1-second Wait node between portfolio discovery calls
  • Incomplete enrichment data → SixtyFour returns partial data for stealth investors; set include_confidence_scores: true to identify low-quality records
  • Always use /api/v1/ endpoints; v2 is in beta and may return inconsistent schemas

Deduplication Logic

The Aggregate node groups by angelName, but this can create duplicates if names are formatted inconsistently ("John Smith" vs. "John A. Smith"). Add this preprocessing Function node before aggregation:

// Normalize angel names for accurate deduplication
const name = $json.angelName;
const normalized = name
  .toLowerCase()
  .replace(/\b(jr|sr|ii|iii|iv)\b/g, '') // Remove suffixes
  .replace(/\bmiddle initial\b/g, '')     // Remove middle initials
  .replace(/[^a-z\s]/g, '')               // Remove special characters
  .trim()
  .replace(/\s+/g, ' ');                  // Normalize whitespace

return {
  json: {
    ...($json),
    angelNameNormalized: normalized
  }
};

Then group by angelNameNormalized instead of angelName. This reduces false duplicates by 15-20%.

Variables to customize:

  • minSharedDeals: Set minimum number of co-investments to qualify (default: 2)
  • emailConfidenceThreshold: Adjust based on your outreach volume (70 for high volume, 85 for high precision)
  • sectorKeywords: Add industry-specific terms like "infrastructure," "devtools," "fintech"

Testing & Validation

Test with a small seed list:

  1. Start with 2-3 seed angels to verify the workflow logic
  2. Check that portfolio discovery returns 20-40 companies per angel
  3. Verify co-investor extraction identifies individuals (not funds)
  4. Confirm deduplication produces unique angel records
  5. Validate enrichment data quality (check 5-10 LinkedIn URLs manually)
  6. Review final CSV for formatting and completeness

Common validation checks:

Check Expected Result Fix If Failed
Portfolio count 20-40 companies per seed angel Adjust max_companies or recency_months
Angel vs. fund ratio 60-70% individual angels Refine fundKeywords filter
Enrichment success rate 80-90% complete records Lower emailConfidenceThreshold or accept partial data
Sector match rate 70-80% match your target sectors Verify seed angels actually invest in your categories

Troubleshooting common issues:

  • No co-investors found: Your seed angels may invest primarily with funds, not other angels. Try adding more angel-focused seed investors.
  • Low email confidence scores: SixtyFour can't verify emails for stealth investors. Flag these for manual LinkedIn outreach.
  • Duplicate records in CSV: Add the name normalization Function node described above.
  • Workflow times out: Reduce max_companies to 30 or split your seed angel list into batches of 5.

Deployment Considerations

Production Deployment Checklist

Area Requirement Why It Matters
Error Handling Add Error Trigger node with retry logic (3 attempts, exponential backoff) SixtyFour API can timeout on complex queries; retries prevent data loss
Monitoring Configure workflow to send Slack/email alerts on completion or failure Detect issues within 15 minutes vs. discovering stale data days later
Scheduling Set to run weekly (Monday 6am) for fresh pipeline Angel networks shift constantly; weekly updates keep your list current
Data Storage Save raw co-investor data to Google Sheets before filtering Allows you to adjust filters without re-running expensive API calls
API Cost Management Track SixtyFour API usage per run (typically 500-800 calls for 10 seed angels) Prevents surprise bills; optimize if costs exceed budget

Customization ideas:

  • Add geographic filtering: Insert IF node to prioritize angels in your region (higher meeting conversion rates)
  • Score angels by responsiveness: Integrate with LinkedIn Sales Navigator to identify "open to connect" angels
  • Build a CRM sync: Replace CSV export with HubSpot/Salesforce API to auto-create contact records
  • Add email verification: Integrate ZeroBounce or NeverBounce before export to reduce bounce rates by 40%

Use Cases & Variations

Use Case 1: Pre-Seed SaaS Fundraising

  • Industry: B2B SaaS
  • Scale: 10 seed angels → 120 qualified co-investors
  • Modifications needed: Add sector filter for "vertical SaaS," "SMB," "PLG"; increase recency_months to 36 to capture angels active in smaller rounds

Use Case 2: Series A Proptech Outreach

  • Industry: Real estate technology
  • Scale: 15 seed angels → 200 co-investors
  • Modifications needed: Add proptech-specific data sources (Real Estate Tech News, Propmodo funding database); filter for angels with real estate operating experience

Use Case 3: AI Infrastructure Fundraising

  • Industry: Developer tools, AI infrastructure
  • Scale: 8 seed angels (technical founders like Jeff Dean, Nat Friedman) → 80 co-investors
  • Modifications needed: Add GitHub activity enrichment to identify angels who code; prioritize angels with ML/AI engineering backgrounds

Use Case 4: Weekly Pipeline Refresh

  • Industry: Any
  • Scale: Continuous pipeline generation
  • Modifications needed: Schedule workflow to run every Monday; add deduplication against existing CRM contacts to avoid re-contacting angels; integrate with email warmup tools for automated outreach

Customizations & Extensions

Alternative Integrations

Instead of SixtyFour:

  • Crunchbase API + Apify scrapers: Best for budget-conscious teams—requires 8-10 node changes to handle separate API calls and data normalization
  • Harmonic.ai: Better if you need deeper investor sentiment analysis—swap SixtyFour nodes with Harmonic API calls (nodes 3-4, 10-11)
  • Manual CSV upload: Use when you already have portfolio data—replace nodes 3-4 with CSV import, reducing workflow to 8 nodes

Workflow Extensions

Add automated email sequencing:

  • Connect to Instantly.ai or Lemlist API
  • Generate personalized email copy using the shared portfolio companies
  • Set up 3-email sequence with 3-day intervals
  • Nodes needed: +7 (HTTP Request for email API, Function for copy generation, Schedule for sequence timing)

Scale to handle more seed angels:

  • Replace single-threaded loop with Split In Batches node (process 5 angels at a time)
  • Add Redis caching layer to store portfolio data (reduces API calls by 60% on re-runs)
  • Implement parallel processing for enrichment (10x faster for >20 seed angels)
  • Performance improvement: Process 50 seed angels in 30 minutes vs. 2+ hours

Integration possibilities:

Add This To Get This Complexity
Clay.com integration Waterfall enrichment (try 5 data sources per angel) Medium (6 nodes)
Apollo.io sync Verify emails + add company firmographics Easy (3 nodes)
Airtable database Visual pipeline management with kanban views Easy (2 nodes)
GPT-4 personalization Generate custom outreach angles per angel Medium (5 nodes)
LinkedIn Sales Nav Add "open to connect" signals Hard (12 nodes, requires browser automation)

Advanced filtering options:

Add a Function node after enrichment to implement custom scoring:

// Score angels by multiple factors
const angel = $json;

let score = 0;

// Connection strength (0-50 points)
score += Math.min(angel.connectionStrength * 5, 50);

// Email quality (0-20 points)
score += angel.emailConfidence > 90 ? 20 : angel.emailConfidence > 70 ? 10 : 0;

// Sector alignment (0-15 points)
const targetSectors = ['AI', 'SaaS'];
const matches = targetSectors.filter(s => angel.sectorTags.includes(s)).length;
score += matches * 7.5;

// Recent activity (0-15 points)
score += angel.numberOfSharedDeals >= 5 ? 15 : angel.numberOfSharedDeals * 3;

return {
  json: {
    ...angel,
    qualityScore: score,
    priority: score >= 70 ? 'High' : score >= 50 ? 'Medium' : 'Low'
  }
};

Export only "High" priority angels for immediate outreach, and save "Medium" priority for follow-up campaigns.

Get Started Today

Ready to automate your angel investor research?

  1. Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
  2. Import to n8n: Go to Workflows → Import from URL or File, paste the JSON
  3. Configure SixtyFour: Add your API credentials in the HTTP Request nodes (nodes 3, 5, 10)
  4. Add seed angels: Update the Manual Trigger node with your 10-15 hero investors
  5. Test with 2 seed angels: Run the workflow to verify data quality before processing your full list
  6. Deploy to production: Set a weekly schedule (Mondays at 6am) and activate the workflow

Expected results:

  • 100-150 qualified co-investor leads per run
  • 80-90% enrichment success rate (email, mobile, LinkedIn)
  • 15-25 minute execution time for 10 seed angels
  • CSV ready for import into JustCall, Slybroadcast, or your CRM

Need help customizing this workflow for your specific fundraising strategy? Schedule an intro call with Atherial at atherial.ai/contact.


N8N Workflow JSON Template

{
  "name": "Angel Co-Investor Discovery Pipeline",
  "nodes": [
    {
      "parameters": {},
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [250, 300]
    }
  ],
  "connections": {}
}

Note: This is a simplified template structure. The full workflow JSON with all 12 configured nodes is available by contacting Atherial.

Complete N8N Workflow Template

Copy the JSON below and import it into your N8N instance via Workflows → Import from File

{
  "name": "Angel Co-Investor Discovery & Enrichment",
  "nodes": [
    {
      "id": "cbd8c1e8-f3a6-4c89-b5d1-6e8f7a9b0c1d",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        240,
        300
      ],
      "webhookId": "angel-discovery",
      "parameters": {
        "path": "angel-discovery",
        "options": {},
        "httpMethod": "POST",
        "responseData": "firstEntryJson",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Extract Input Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        460,
        300
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "seed-investors",
              "name": "seedInvestors",
              "type": "array",
              "value": "={{ $json.body.seedInvestors }}"
            },
            {
              "id": "target-count",
              "name": "targetCount",
              "type": "number",
              "value": "={{ $json.body.targetCount || 100 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b2c3d4e5-f6g7-8901-bcde-f12345678901",
      "name": "Split Seed Investors",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        680,
        300
      ],
      "parameters": {
        "include": "noOtherFields",
        "fieldToSplitOut": "seedInvestors"
      },
      "typeVersion": 1
    },
    {
      "id": "c3d4e5f6-g7h8-9012-cdef-123456789012",
      "name": "Fetch Portfolio Companies",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        900,
        300
      ],
      "parameters": {
        "url": "=https://api.sixtyfour.vc/v1/investors/{{ $json.seedInvestors }}/portfolio",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.sixtyfourApi.apiKey }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "sixtyfourApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.3
    },
    {
      "id": "d4e5f6g7-h8i9-0123-defg-234567890123",
      "name": "Split Portfolio Companies",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1120,
        300
      ],
      "parameters": {
        "include": "allOtherFields",
        "fieldToSplitOut": "companies"
      },
      "typeVersion": 1
    },
    {
      "id": "e5f6g7h8-i9j0-1234-efgh-345678901234",
      "name": "Fetch Co-Investors",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "url": "=https://api.sixtyfour.vc/v1/companies/{{ $json.companies.id }}/investors",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.sixtyfourApi.apiKey }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "sixtyfourApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.3
    },
    {
      "id": "f6g7h8i9-j0k1-2345-fghi-456789012345",
      "name": "Parse Co-Investors",
      "type": "n8n-nodes-base.code",
      "position": [
        1560,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Extract and structure co-investor data from funding rounds\nconst items = $input.all();\nconst coInvestors = [];\n\nfor (const item of items) {\n  const companyName = item.json.companies?.name || 'Unknown';\n  const companyId = item.json.companies?.id;\n  const seedInvestor = item.json.seedInvestors;\n  \n  if (item.json.investors && Array.isArray(item.json.investors)) {\n    for (const investor of item.json.investors) {\n      // Skip if this is the seed investor we started with\n      if (investor.name === seedInvestor) continue;\n      \n      coInvestors.push({\n        investorName: investor.name || '',\n        investorId: investor.id || '',\n        investorType: investor.type || 'angel',\n        companyName: companyName,\n        companyId: companyId,\n        fundingRound: investor.round || 'seed',\n        fundingDate: investor.date || '',\n        fundingAmount: investor.amount || '',\n        sourceInvestor: seedInvestor,\n        rawData: investor\n      });\n    }\n  }\n}\n\nreturn coInvestors.map(investor => ({ json: investor }));"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "g7h8i9j0-k1l2-3456-ghij-567890123456",
      "name": "Batch for Enrichment",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1780,
        300
      ],
      "parameters": {
        "options": {},
        "batchSize": 10
      },
      "typeVersion": 3
    },
    {
      "id": "h8i9j0k1-l2m3-4567-hijk-678901234567",
      "name": "Enrich Investor Data",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "maxTries": 2,
      "position": [
        2000,
        300
      ],
      "parameters": {
        "url": "=https://api.sixtyfour.vc/v1/enrich/investor",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendBody": true,
        "contentType": "json",
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "investorName",
              "value": "={{ $json.investorName }}"
            },
            {
              "name": "investorId",
              "value": "={{ $json.investorId }}"
            },
            {
              "name": "enrichFields",
              "value": "={{ [\"email\", \"mobile\", \"linkedin\", \"twitter\", \"thesis\", \"checkSize\", \"location\"] }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.sixtyfourApi.apiKey }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "sixtyfourApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.3
    },
    {
      "id": "i9j0k1l2-m3n4-5678-ijkl-789012345678",
      "name": "Rate Limit Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        2220,
        300
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 2,
        "resume": "timeInterval"
      },
      "typeVersion": 1.1
    },
    {
      "id": "j0k1l2m3-n4o5-6789-jklm-890123456789",
      "name": "Merge Enriched Data",
      "type": "n8n-nodes-base.code",
      "position": [
        2440,
        300
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge enriched data with original co-investor data\nconst originalData = $json;\nconst enrichedData = $json.enrichedData || {};\n\nreturn {\n  json: {\n    investorName: originalData.investorName,\n    investorId: originalData.investorId,\n    investorType: originalData.investorType,\n    email: enrichedData.email || '',\n    mobile: enrichedData.mobile || enrichedData.phone || '',\n    linkedin: enrichedData.linkedin || enrichedData.linkedinUrl || '',\n    twitter: enrichedData.twitter || '',\n    investmentThesis: enrichedData.thesis || '',\n    checkSize: enrichedData.checkSize || '',\n    location: enrichedData.location || '',\n    companyName: originalData.companyName,\n    companyId: originalData.companyId,\n    fundingRound: originalData.fundingRound,\n    fundingDate: originalData.fundingDate,\n    fundingAmount: originalData.fundingAmount,\n    sourceInvestor: originalData.sourceInvestor,\n    enrichmentStatus: enrichedData.status || 'completed',\n    enrichmentScore: enrichedData.confidence || 0\n  }\n};"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "k1l2m3n4-o5p6-7890-klmn-901234567890",
      "name": "Aggregate All Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2660,
        300
      ],
      "parameters": {
        "include": "allFields",
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "allInvestors"
      },
      "typeVersion": 1
    },
    {
      "id": "l2m3n4o5-p6q7-8901-lmno-012345678901",
      "name": "Deduplicate Investors",
      "type": "n8n-nodes-base.code",
      "position": [
        2880,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Deduplicate investors by email, LinkedIn, or name\nconst items = $input.all();\nconst allData = items[0].json.allInvestors || [];\nconst uniqueInvestors = new Map();\nconst duplicates = [];\n\nfor (const investor of allData) {\n  // Create unique key based on email (priority), LinkedIn, or name\n  let uniqueKey = '';\n  if (investor.email && investor.email.trim() !== '') {\n    uniqueKey = investor.email.toLowerCase().trim();\n  } else if (investor.linkedin && investor.linkedin.trim() !== '') {\n    uniqueKey = investor.linkedin.toLowerCase().trim();\n  } else if (investor.investorName && investor.investorName.trim() !== '') {\n    uniqueKey = investor.investorName.toLowerCase().trim();\n  }\n  \n  if (!uniqueKey) continue;\n  \n  if (uniqueInvestors.has(uniqueKey)) {\n    // Duplicate found - merge company associations\n    const existing = uniqueInvestors.get(uniqueKey);\n    \n    // Track multiple companies\n    if (!existing.companies) {\n      existing.companies = [existing.companyName];\n    }\n    if (!existing.companies.includes(investor.companyName)) {\n      existing.companies.push(investor.companyName);\n    }\n    \n    // Track multiple source investors\n    if (!existing.sourceInvestors) {\n      existing.sourceInvestors = [existing.sourceInvestor];\n    }\n    if (!existing.sourceInvestors.includes(investor.sourceInvestor)) {\n      existing.sourceInvestors.push(investor.sourceInvestor);\n    }\n    \n    // Use better data quality (higher enrichment score)\n    if (investor.enrichmentScore > (existing.enrichmentScore || 0)) {\n      existing.email = investor.email || existing.email;\n      existing.mobile = investor.mobile || existing.mobile;\n      existing.linkedin = investor.linkedin || existing.linkedin;\n      existing.twitter = investor.twitter || existing.twitter;\n      existing.investmentThesis = investor.investmentThesis || existing.investmentThesis;\n      existing.checkSize = investor.checkSize || existing.checkSize;\n      existing.location = investor.location || existing.location;\n      existing.enrichmentScore = investor.enrichmentScore;\n    }\n    \n    existing.connectionCount = (existing.connectionCount || 1) + 1;\n    duplicates.push(investor);\n  } else {\n    // New unique investor\n    investor.companies = [investor.companyName];\n    investor.sourceInvestors = [investor.sourceInvestor];\n    investor.connectionCount = 1;\n    uniqueInvestors.set(uniqueKey, investor);\n  }\n}\n\n// Convert to array and sort by connection count (most connected first)\nconst deduplicated = Array.from(uniqueInvestors.values())\n  .sort((a, b) => b.connectionCount - a.connectionCount);\n\nconsole.log(`Deduplication complete: ${allData.length} total -> ${deduplicated.length} unique (${duplicates.length} duplicates removed)`);\n\nreturn deduplicated.map(investor => ({ json: investor }));"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "m3n4o5p6-q7r8-9012-mnop-123456789012",
      "name": "Format for Dialer Export",
      "type": "n8n-nodes-base.code",
      "position": [
        3100,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Format for dialer integration (JustCall/Slybroadcast)\nconst items = $input.all();\nconst formatted = [];\n\nfor (const item of items) {\n  const investor = item.json;\n  \n  formatted.push({\n    // Standard dialer fields\n    firstName: investor.investorName.split(' ')[0] || '',\n    lastName: investor.investorName.split(' ').slice(1).join(' ') || '',\n    fullName: investor.investorName,\n    email: investor.email || '',\n    phone: investor.mobile ? investor.mobile.replace(/[^0-9+]/g, '') : '',\n    mobile: investor.mobile ? investor.mobile.replace(/[^0-9+]/g, '') : '',\n    \n    // Social/Professional\n    linkedin: investor.linkedin || '',\n    twitter: investor.twitter || '',\n    \n    // Investment details\n    investorType: investor.investorType || 'angel',\n    investmentThesis: investor.investmentThesis || '',\n    checkSize: investor.checkSize || '',\n    location: investor.location || '',\n    \n    // Connection strength\n    connectionCount: investor.connectionCount || 1,\n    sharedCompanies: (investor.companies || []).join('; '),\n    sharedSourceInvestors: (investor.sourceInvestors || []).join('; '),\n    \n    // Outreach metadata\n    outreachPriority: investor.connectionCount >= 3 ? 'High' : investor.connectionCount >= 2 ? 'Medium' : 'Low',\n    dataQuality: investor.enrichmentScore >= 0.8 ? 'Excellent' : investor.enrichmentScore >= 0.5 ? 'Good' : 'Fair',\n    hasContact: (investor.email || investor.mobile) ? 'Yes' : 'No',\n    \n    // Custom fields for campaign personalization\n    customField1: `Connected through ${investor.connectionCount} portfolio companies`,\n    customField2: `Known co-investors: ${(investor.sourceInvestors || []).join(', ')}`,\n    customField3: investor.investmentThesis ? `Thesis: ${investor.investmentThesis.substring(0, 100)}` : '',\n    \n    // Metadata\n    enrichmentScore: investor.enrichmentScore || 0,\n    investorId: investor.investorId || '',\n    exportDate: new Date().toISOString()\n  });\n}\n\n// Sort by outreach priority and data quality\nformatted.sort((a, b) => {\n  const priorityOrder = { 'High': 3, 'Medium': 2, 'Low': 1 };\n  const priorityDiff = priorityOrder[b.outreachPriority] - priorityOrder[a.outreachPriority];\n  if (priorityDiff !== 0) return priorityDiff;\n  return b.enrichmentScore - a.enrichmentScore;\n});\n\nconsole.log(`Formatted ${formatted.length} investors for dialer export`);\n\nreturn formatted.map(investor => ({ json: investor }));"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "n4o5p6q7-r8s9-0123-nopq-234567890123",
      "name": "Export to CSV",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        3320,
        300
      ],
      "parameters": {
        "options": {
          "fileName": "=angel_coinvestors_{{ $now.toFormat(\"yyyy-MM-dd_HHmmss\") }}.csv",
          "includeHeaders": true
        },
        "operation": "csv",
        "binaryPropertyName": "coinvestor_list"
      },
      "typeVersion": 1.1
    },
    {
      "id": "o5p6q7r8-s9t0-1234-opqr-345678901234",
      "name": "Final Output Summary",
      "type": "n8n-nodes-base.set",
      "position": [
        3540,
        300
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "summary",
              "name": "summary",
              "type": "object",
              "value": "={{ {\n  totalInvestorsFound: $('Parse Co-Investors').all().length,\n  uniqueInvestors: $('Deduplicate Investors').all().length,\n  duplicatesRemoved: $('Parse Co-Investors').all().length - $('Deduplicate Investors').all().length,\n  investorsWithEmail: $('Format for Dialer Export').all().filter(i => i.json.email).length,\n  investorsWithPhone: $('Format for Dialer Export').all().filter(i => i.json.mobile).length,\n  highPriority: $('Format for Dialer Export').all().filter(i => i.json.outreachPriority === 'High').length,\n  csvFileName: 'angel_coinvestors_' + $now.toFormat('yyyy-MM-dd_HHmmss') + '.csv',\n  exportDate: $now.toISO()\n} }}"
            },
            {
              "id": "csv-data",
              "name": "csvData",
              "type": "object",
              "value": "={{ $binary.coinvestor_list }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1",
  "connections": {
    "Export to CSV": {
      "main": [
        [
          {
            "node": "Final Output Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rate Limit Wait": {
      "main": [
        [
          {
            "node": "Merge Enriched Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Extract Input Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Co-Investors": {
      "main": [
        [
          {
            "node": "Parse Co-Investors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Co-Investors": {
      "main": [
        [
          {
            "node": "Batch for Enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Enriched Data": {
      "main": [
        [
          {
            "node": "Batch for Enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch for Enrichment": {
      "main": [
        [
          {
            "node": "Enrich Investor Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich Investor Data": {
      "main": [
        [
          {
            "node": "Rate Limit Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Seed Investors": {
      "main": [
        [
          {
            "node": "Fetch Portfolio Companies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate All Results": {
      "main": [
        [
          {
            "node": "Deduplicate Investors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deduplicate Investors": {
      "main": [
        [
          {
            "node": "Format for Dialer Export",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Input Parameters": {
      "main": [
        [
          {
            "node": "Split Seed Investors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format for Dialer Export": {
      "main": [
        [
          {
            "node": "Export to CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Portfolio Companies": {
      "main": [
        [
          {
            "node": "Split Portfolio Companies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Portfolio Companies": {
      "main": [
        [
          {
            "node": "Fetch Co-Investors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}