{
  "name": "LinkedIn Outreach: PhantomBuster + OpenAI + Human Review (Fixed)",
  "nodes": [
    {
      "parameters": {},
      "id": "ManualTrigger",
      "name": "Start (Manual)",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        200,
        200
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [],
          "number": [],
          "boolean": [],
          "json": [
            {
              "name": "contacts",
              "value": [
                {
                  "profileUrl": "https://www.linkedin.com/in/REPLACE_CONTACT_1/"
                },
                {
                  "profileUrl": "https://www.linkedin.com/in/REPLACE_CONTACT_2/"
                }
              ]
            }
          ]
        }
      },
      "id": "ContactsSet",
      "name": "Contacts (Set)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        400,
        200
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "SplitBatches",
      "name": "Split in Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        600,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "const list = items[0].json.contacts || [];\nconst i = $runOnce().get('idx') ?? 0;\n$runOnce().set('idx', i + 1);\nif (!list[i]) { return []; }\nreturn [{ json: { contactId: i+1, profileUrl: list[i].profileUrl } }];"
      },
      "id": "CurrentContact",
      "name": "Current Contact",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        800,
        200
      ]
    },
    {
      "parameters": {
        "url": "https://api.phantombuster.com/api/v2/agents/launch",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Phantombuster-Key-1",
              "value": "O1jLrrHNXybkStIYIkX09htdCY9bYlfUsHrniF58uLw"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyContentType": "json",
        "jsonBody": "={\"id\":\"SCRAPER_AGENT_ID\",\"argument\":{\"profileUrls\":[\"{{$json.profileUrl}}\"],\"spreadsheetUrl\":null}}"
      },
      "id": "PB_LaunchScraper",
      "name": "PhantomBuster: Launch Scraper",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1000,
        200
      ]
    },
    {
      "parameters": {
        "amount": 25,
        "unit": "seconds"
      },
      "id": "Wait_Scrape",
      "name": "Wait (Scrape)",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        1200,
        200
      ]
    },
    {
      "parameters": {
        "url": "https://api.phantombuster.com/api/v2/agents/fetch-output?id=SCRAPER_AGENT_ID",
        "method": "GET",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Phantombuster-Key-1",
              "value": "O1jLrrHNXybkStIYIkX09htdCY9bYlfUsHrniF58uLw"
            }
          ]
        }
      },
      "id": "PB_FetchOutput",
      "name": "PhantomBuster: Fetch Output",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1400,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "const out = items[0].json || {};\nconst row = (out.resultObject && out.resultObject.data && out.resultObject.data[0]) || (out.data && out.data[0]) || {};\nreturn [{ json: {\n  contactId: $json.contactId || 0,\n  profileUrl: $json.profileUrl || '',\n  fullName: row.fullName || row.name || '',\n  headline: row.headline || row.jobTitle || '',\n  company: row.company || row.currentCompany || '',\n  summary: row.summary || row.about || '',\n  skills: row.skills || [],\n  experience: row.experiences || row.experience || []\n}}];"
      },
      "id": "ExtractProfile",
      "name": "Extract Profile Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1600,
        200
      ]
    },
    {
      "parameters": {
        "keepOnlySet": false,
        "values": {
          "string": [
            {
              "name": "template",
              "value": "Hi {{fullName}}, loved your work at {{company}} as {{headline}}. I'm reaching out because {{reason}}. Based on your background in {{skillList}}, I think this could be valuable. Would you be open to a quick chat?"
            },
            {
              "name": "reason",
              "value": "we help {{company}}-type teams automate outreach without losing personalization"
            }
          ],
          "number": [],
          "boolean": [],
          "json": []
        }
      },
      "id": "MessageTemplate",
      "name": "Message Template (Set)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        1800,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "const skills = Array.isArray($json.skills) ? $json.skills.slice(0,6).join(', ') : String($json.skills||'');\nconst t = $json.template || '';\nconst filled = t\n  .replaceAll('{{fullName}}', $json.fullName || 'there')\n  .replaceAll('{{company}}', $json.company || 'your company')\n  .replaceAll('{{headline}}', $json.headline || 'your role')\n  .replaceAll('{{skillList}}', skills)\n  .replaceAll('{{reason}}', $json.reason || 'we might collaborate');\nconst system = 'You craft concise, friendly LinkedIn outreach. 280 characters max. Keep it professional and specific.';\nconst user = `PROFILE:\\nName: ${$json.fullName}\\nHeadline: ${$json.headline}\\nCompany: ${$json.company}\\nSummary: ${(String($json.summary||'')).slice(0,500)}\\nTop skills: ${skills}\\n\\nTEMPLATE (adapt under 280 chars):\\n${filled}`;\nreturn [{ json: { contactId: $json.contactId, profileUrl: $json.profileUrl, fullName: $json.fullName, prompt: { system, user }, seedTemplate: t, filledTemplate: filled } }];"
      },
      "id": "BuildPrompt",
      "name": "Build Prompt",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        2000,
        200
      ]
    },
    {
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer OPENAI_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyContentType": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"messages\": [\n    {\"role\": \"system\", \"content\": $json.prompt.system },\n    {\"role\": \"user\", \"content\": $json.prompt.user }\n  ],\n  \"temperature\": 0.7\n}"
      },
      "id": "OpenAIChat",
      "name": "OpenAI: Chat",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        2200,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "const c = items[0].json || {};\nconst msg = (c.choices && c.choices[0] && c.choices[0].message && c.choices[0].message.content) || '';\nreturn [{ json: { contactId: $json.contactId, profileUrl: $json.profileUrl, fullName: $json.fullName, draft: msg } }];"
      },
      "id": "CollectDraft",
      "name": "Collect Draft",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        2400,
        200
      ]
    },
    {
      "parameters": {
        "httpMethod": "GET",
        "path": "linkedin-approve",
        "options": {}
      },
      "id": "WebhookApproval",
      "name": "Webhook (Approve/Reject)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        2600,
        200
      ]
    },
    {
      "parameters": {
        "functionCode": "const qs = $json.query || {};\nconst approved = String(qs.approve || '').toLowerCase() === 'true';\nreturn [{ json: { approved, contactId: $json.contactId, profileUrl: $json.profileUrl, draft: $json.draft } }];"
      },
      "id": "ParseApproval",
      "name": "Parse Approval",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        2800,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition1",
              "leftValue": "={{$json.approved}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "IfApproved",
      "name": "IF Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        3000,
        200
      ]
    },
    {
      "parameters": {
        "url": "https://api.phantombuster.com/api/v2/agents/launch",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Phantombuster-Key-1",
              "value": "O1jLrrHNXybkStIYIkX09htdCY9bYlfUsHrniF58uLw"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyContentType": "json",
        "jsonBody": "={\"id\":\"SENDER_AGENT_ID\",\"argument\":{\"leads\":[{\"profileUrl\":\"{{$json.profileUrl}}\",\"message\":\"{{$json.draft}}\"}]}}"
      },
      "id": "PB_SendMessage",
      "name": "PhantomBuster: Launch Message Sender",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        3200,
        130
      ]
    },
    {
      "parameters": {},
      "id": "Rejected",
      "name": "Rejected (Log)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        3200,
        270
      ]
    },
    {
      "parameters": {},
      "id": "Done",
      "name": "Done",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        3400,
        200
      ]
    }
  ],
  "connections": {
    "Start (Manual)": {
      "main": [
        [
          {
            "node": "Contacts (Set)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contacts (Set)": {
      "main": [
        [
          {
            "node": "Split in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split in Batches": {
      "main": [
        [
          {
            "node": "Current Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Current Contact": {
      "main": [
        [
          {
            "node": "PhantomBuster: Launch Scraper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PhantomBuster: Launch Scraper": {
      "main": [
        [
          {
            "node": "Wait (Scrape)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait (Scrape)": {
      "main": [
        [
          {
            "node": "PhantomBuster: Fetch Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PhantomBuster: Fetch Output": {
      "main": [
        [
          {
            "node": "Extract Profile Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Profile Data": {
      "main": [
        [
          {
            "node": "Message Template (Set)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message Template (Set)": {
      "main": [
        [
          {
            "node": "Build Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Prompt": {
      "main": [
        [
          {
            "node": "OpenAI: Chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI: Chat": {
      "main": [
        [
          {
            "node": "Collect Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect Draft": {
      "main": [
        [
          {
            "node": "Webhook (Approve/Reject)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook (Approve/Reject)": {
      "main": [
        [
          {
            "node": "Parse Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Approval": {
      "main": [
        [
          {
            "node": "IF Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Approved?": {
      "main": [
        [
          {
            "node": "PhantomBuster: Launch Message Sender",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rejected (Log)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PhantomBuster: Launch Message Sender": {
      "main": [
        [
          {
            "node": "Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rejected (Log)": {
      "main": [
        [
          {
            "node": "Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}