{
  "id": "a2zGiFAlPVt1SNgz",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Apollo LinkedIn Profile Viewer",
  "tags": [],
  "nodes": [
    {
      "id": "694b75fc-cb9f-45d6-b1a7-6153ddb57bdd",
      "name": "Receive LinkedIn URL via Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -848,
        512
      ],
      "parameters": {
        "path": "compose-workflow",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 1
    },
    {
      "id": "24433e6b-4ac3-4848-90a8-5f3af0752e61",
      "name": "Fetch Profile from Apollo API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -576,
        512
      ],
      "parameters": {
        "url": "=https://api.apollo.io/api/v1/people/match?reveal_personal_emails=false&reveal_phone_number=false' \\",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "linkedin_url",
              "value": "={{ $json.body.linkedin_url }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "hGMCJPhktjuW1qIAxHQesw"
            },
            {
              "name": "accept",
              "value": "application/json"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "4781e833-0d78-410f-8f5c-d010933e7329",
      "name": "Normalize & Extract Profile Fields",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        -208,
        512
      ],
      "parameters": {
        "jsCode": "const person = $json.person;\n\nif (!person) {\n  return [];\n}\n\n// Get current job\nconst currentJob = (person.employment_history || []).find(job => job.current === true) || {};\n\n// Build clean output\nreturn [\n  {\n    full_name: person.name || null,\n    first_name: person.first_name || null,\n    last_name: person.last_name || null,\n    linkedin_url: person.linkedin_url || null,\n    title: person.title || null,\n    headline: person.headline || null,\n    email: person.email || null,\n    email_status: person.email_status || null,\n    city: person.city || null,\n    state: person.state || null,\n    country: person.country || null,\n    seniority: person.seniority || null,\n\n    current_company: currentJob.organization_name || null,\n    current_role: currentJob.title || null,\n    current_role_start_date: currentJob.start_date || null,\n\n    total_experience_count: (person.employment_history || []).length,\n\n    organization_name: person.organization?.name || null,\n    organization_industry: person.organization?.industry || null,\n    organization_employee_count: person.organization?.estimated_num_employees || null,\n    organization_website: person.organization?.website_url || null,\n    organization_linkedin: person.organization?.linkedin_url || null,\n\n    organization_technologies: person.organization?.technology_names || []\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "40e8cb6b-ef61-4f22-87cc-4184ca9af63b",
      "name": "Build HTML Profile Cards",
      "type": "n8n-nodes-base.code",
      "position": [
        128,
        512
      ],
      "parameters": {
        "jsCode": "// Collect all profiles dynamically\nconst profiles = items.map(item => item.json);\n\n// Safe value helper\nconst safe = (val) => {\n  if (!val) return \"N/A\";\n  return val;\n};\n\n// Generate dynamic cards\nconst cards = profiles.map(profile => {\n\n  const techStack = (profile.organization_technologies || [])\n    .map(tech => `<span class=\"tech\">${tech}</span>`)\n    .join(\"\");\n\n  return `\n    <div class=\"card\">\n      <div class=\"header\">\n        <div class=\"name\">${safe(profile.full_name)}</div>\n        <div class=\"role\">${safe(profile.title)}</div>\n        <div class=\"headline\">${safe(profile.headline)}</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Email:</strong> ${safe(profile.email)} (${safe(profile.email_status)})</div>\n        <div><strong>Location:</strong> ${safe(profile.city)}, ${safe(profile.state)}, ${safe(profile.country)}</div>\n        <div><strong>Seniority:</strong> ${safe(profile.seniority)}</div>\n        <div><strong>Total Experience:</strong> ${safe(profile.total_experience_count)} roles</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Current Company:</strong> ${safe(profile.current_company)}</div>\n        <div><strong>Current Role:</strong> ${safe(profile.current_role)}</div>\n        <div><strong>Start Date:</strong> ${safe(profile.current_role_start_date)}</div>\n      </div>\n\n      <div class=\"section\">\n        <div><strong>Industry:</strong> ${safe(profile.organization_industry)}</div>\n        <div><strong>Employees:</strong> ${safe(profile.organization_employee_count)}</div>\n        <div><strong>Website:</strong> <a href=\"${safe(profile.organization_website)}\" target=\"_blank\">${safe(profile.organization_website)}</a></div>\n        <div><strong>LinkedIn:</strong> <a href=\"${safe(profile.linkedin_url)}\" target=\"_blank\">View Profile</a></div>\n      </div>\n\n      <div class=\"section\">\n        <strong>Technology Stack:</strong>\n        <div class=\"tech-container\">\n          ${techStack || \"N/A\"}\n        </div>\n      </div>\n    </div>\n  `;\n}).join(\"\");\n\n\n// Final HTML page\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Candidate Profiles</title>\n<style>\nbody {\n  font-family: 'Segoe UI', sans-serif;\n  background: #f4f6f9;\n  padding: 40px;\n}\n.card {\n  background: #ffffff;\n  padding: 25px;\n  margin-bottom: 30px;\n  border-radius: 14px;\n  box-shadow: 0 6px 18px rgba(0,0,0,0.08);\n}\n.header {\n  margin-bottom: 15px;\n}\n.name {\n  font-size: 22px;\n  font-weight: 600;\n}\n.role {\n  color: #444;\n  margin-top: 4px;\n}\n.headline {\n  font-size: 14px;\n  color: #777;\n  margin-top: 6px;\n}\n.section {\n  margin-top: 18px;\n  font-size: 14px;\n}\n.tech-container {\n  margin-top: 10px;\n}\n.tech {\n  display: inline-block;\n  background: #eef2ff;\n  padding: 6px 10px;\n  margin: 4px;\n  border-radius: 6px;\n  font-size: 12px;\n}\na {\n  color: #3b82f6;\n  text-decoration: none;\n}\na:hover {\n  text-decoration: underline;\n}\n</style>\n</head>\n<body>\n<h2>Candidate Profiles</h2>\n${cards}\n</body>\n</html>\n`;\n\nreturn [\n  {\n    json: {\n      html: html\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "3ad6eded-fcba-4305-bf5e-5f0ee789fb4c",
      "name": "Return HTML Profile Page",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        336,
        512
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{$node['Build HTML Profile Cards'].json['html']}}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "30e3b485-ed0b-48e8-aebb-6886be7f4cae",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1520,
        208
      ],
      "parameters": {
        "color": 3,
        "width": 520,
        "height": 804,
        "content": "## \ud83d\udd0d Apollo LinkedIn Profile Viewer\n\n### How it works\nThis workflow accepts a LinkedIn profile URL via a POST webhook request and returns a fully rendered HTML page displaying enriched candidate profile data sourced from the Apollo.io API.\n\n1. **Receive LinkedIn URL via Webhook** \u2014 A POST request is sent to the webhook endpoint with a `linkedin_url` in the request body.\n2. **Fetch Profile from Apollo API** \u2014 The LinkedIn URL is passed to Apollo's `/people/match` endpoint to retrieve enriched person and organization data.\n3. **Normalize & Extract Profile Fields** \u2014 Raw Apollo response is cleaned and structured into flat, usable fields (name, title, email, company, tech stack, etc.).\n4. **Build HTML Profile Cards** \u2014 Structured profile data is rendered into a styled HTML card layout.\n5. **Return HTML Profile Page** \u2014 The final HTML is sent back as the webhook response, which can be rendered directly in a browser.\n\n### Setup steps\n1. Copy the webhook URL from the **Receive LinkedIn URL via Webhook** node and use it as your POST endpoint.\n2. Replace the hardcoded `x-api-key` value in **Fetch Profile from Apollo API** with your own Apollo.io API key.\n3. Activate the workflow.\n4. Send a POST request with body `{ \"linkedin_url\": \"https://linkedin.com/in/example\" }` to receive the HTML response.\n\n### Customization\n- Modify the HTML/CSS inside **Build HTML Profile Cards** to match your brand or layout preferences.\n- Add additional Apollo fields by extending the mapping in **Normalize & Extract Profile Fields**."
      },
      "typeVersion": 1
    },
    {
      "id": "f334da27-2eed-40dd-a826-b52b46086fce",
      "name": "Section \u2013 Webhook Entry",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        288
      ],
      "parameters": {
        "width": 260,
        "height": 200,
        "content": "## \ud83d\udce5 Webhook Entry Point\nReceives a POST request containing a `linkedin_url` in the body. This is the sole trigger for the workflow \u2014 no scheduler or manual trigger is used."
      },
      "typeVersion": 1
    },
    {
      "id": "1a7cf214-3ba9-4866-9615-ae88544a2e12",
      "name": "Warning \u2013 Apollo API Key",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        736
      ],
      "parameters": {
        "color": 2,
        "width": 280,
        "height": 180,
        "content": "\u26a0\ufe0f **Apollo API Key Required**\nReplace the hardcoded `x-api-key` header value with your own Apollo.io API key. Using an invalid or expired key will cause this node to fail silently or return empty data. Monitor your Apollo plan's rate limits to avoid unexpected quota exhaustion."
      },
      "typeVersion": 1
    },
    {
      "id": "b583ab77-39cf-4a8a-85b7-10cc0deb9cc5",
      "name": "Section \u2013 Data Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        288
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\udd04 Data Processing\nNormalizes the raw Apollo API response and maps person and organization fields into a clean flat object. Errors are suppressed \u2014 if no `person` object is returned, the node outputs nothing and the workflow stops gracefully."
      },
      "typeVersion": 1
    },
    {
      "id": "fd12f228-2c47-4a5b-b949-b15cbff81292",
      "name": "Section \u2013 HTML Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        288
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\uddbc\ufe0f HTML Rendering & Response\nGenerates a styled multi-card HTML page from the normalized profile data, then returns it directly as the webhook HTTP response. The output can be opened in any browser."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "568161c8-6d2d-4b61-a49b-cad70cd5da45",
  "connections": {
    "Build HTML Profile Cards": {
      "main": [
        [
          {
            "node": "Return HTML Profile Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Profile from Apollo API": {
      "main": [
        [
          {
            "node": "Normalize & Extract Profile Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive LinkedIn URL via Webhook": {
      "main": [
        [
          {
            "node": "Fetch Profile from Apollo API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize & Extract Profile Fields": {
      "main": [
        [
          {
            "node": "Build HTML Profile Cards",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}