{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "nodes": [
    {
      "id": "11111111-0000-0000-0000-000000000001",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2600,
        100
      ],
      "parameters": {
        "color": 3,
        "width": 500,
        "height": 700,
        "content": "## Scrape Apollo.io leads and sync to Google Sheets via ScraperCity\n\n**Who is this for:** B2B sales teams and growth operators who want verified contact lists from Apollo.io at $0.0039/contact -- automatically, without manual CSV exports.\n\n**What it does:**\n1. Accepts your target filters (job titles, industry, company size) from the Configure node.\n2. Sends an async scrape job to ScraperCity's Apollo filter endpoint.\n3. Polls every 60 seconds until the scrape completes (jobs take 10-60 min).\n4. Downloads the result, parses CSV rows, deduplicates by email, and writes clean leads to Google Sheets.\n\n**Setup steps:**\n1. Sign up at scrapercity.com and copy your API key.\n2. In n8n -> Credentials, create a \"Header Auth\" credential:\n   - Name: ScraperCity API Key\n   - Header Name: Authorization\n   - Header Value: Bearer YOUR_KEY_HERE\n3. Connect Google Sheets OAuth2 credential.\n4. Open \"Save Leads to Google Sheets\" and set your document ID and sheet name.\n5. Edit \"Configure Search Parameters\" with your ideal customer profile.\n6. Click Execute and wait -- leads appear in Sheets when the job is done.\n\n**Cost estimate:** 1,000 contacts = ~$3.90 USD via ScraperCity."
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000002",
      "name": "Sticky Note - Configuration Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2040,
        -160
      ],
      "parameters": {
        "color": 5,
        "width": 980,
        "height": 330,
        "content": "## Configuration\n\nThe \"When clicking Execute workflow\" trigger starts the run manually. \"Configure Search Parameters\" holds all user-editable variables -- job titles, industry, company size, and lead count -- in one place so you never need to dig into HTTP nodes. Edit this node before your first run. The \"Start Apollo Lead Scrape\" node reads these values and sends them to the ScraperCity filter endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000003",
      "name": "Sticky Note - Async Polling Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -160
      ],
      "parameters": {
        "color": 6,
        "width": 980,
        "height": 330,
        "content": "## Async Polling Loop\n\nScraperCity scrapes run in the background and typically take 10-60 minutes. \"Store Run ID\" saves the job identifier returned by the scrape request. \"Wait 60 Seconds\" pauses execution before each status check. \"Check Scrape Status\" calls the status endpoint, and \"Is Scrape Complete?\" checks whether status equals SUCCEEDED. If not complete, the loop iterates back to the wait node via \"Poll Again\". The splitInBatches node caps iterations to prevent infinite loops."
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000004",
      "name": "Sticky Note - Download and Output Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 980,
        "height": 330,
        "content": "## Download and Output\n\nOnce the scrape status is SUCCEEDED, \"Download Scraped Results\" fetches the CSV file from the ScraperCity download endpoint. \"Parse CSV and Format Leads\" transforms raw CSV text into structured JSON rows, one item per contact. \"Remove Duplicate Leads\" deduplicates by email address. Finally, \"Save Leads to Google Sheets\" appends each unique lead as a new row in your target spreadsheet."
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000005",
      "name": "Sticky Note - Set API Key",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1516,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 70,
        "content": "Add your ScraperCity API key credential here"
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000006",
      "name": "Sticky Note - Set Google Sheet ID",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        734,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 70,
        "content": "Set your Google Sheets document ID and sheet name here"
      },
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000007",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -2000,
        400
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "11111111-0000-0000-0000-000000000008",
      "name": "Configure Search Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        -1750,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "jobTitles",
              "type": "string",
              "value": "CEO,CTO,VP of Sales"
            },
            {
              "id": "cfg-002",
              "name": "industry",
              "type": "string",
              "value": "Technology"
            },
            {
              "id": "cfg-003",
              "name": "companySize",
              "type": "string",
              "value": "11-50"
            },
            {
              "id": "cfg-004",
              "name": "leadCount",
              "type": "number",
              "value": 100
            },
            {
              "id": "cfg-005",
              "name": "exportFileName",
              "type": "string",
              "value": "Apollo Export"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "11111111-0000-0000-0000-000000000009",
      "name": "Start Apollo Lead Scrape",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1500,
        400
      ],
      "parameters": {
        "url": "https://scrapercity.com/api/v1/scrape/apollo-filters",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"personTitles\": {{ JSON.stringify($json.jobTitles.split(',').map(t => t.trim())) }},\n  \"companyIndustry\": \"{{ $json.industry }}\",\n  \"companySize\": \"{{ $json.companySize }}\",\n  \"count\": {{ $json.leadCount }},\n  \"fileName\": \"{{ $json.exportFileName }}\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "11111111-0000-0000-0000-000000000010",
      "name": "Store Run ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -1250,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "rid-001",
              "name": "runId",
              "type": "string",
              "value": "={{ $json.runId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "11111111-0000-0000-0000-000000000011",
      "name": "Polling Loop Controller",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1000,
        400
      ],
      "parameters": {
        "options": {
          "reset": false
        },
        "batchSize": 1
      },
      "typeVersion": 3
    },
    {
      "id": "11111111-0000-0000-0000-000000000012",
      "name": "Wait 60 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        -750,
        400
      ],
      "parameters": {
        "amount": 60
      },
      "typeVersion": 1.1
    },
    {
      "id": "11111111-0000-0000-0000-000000000013",
      "name": "Check Scrape Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -500,
        400
      ],
      "parameters": {
        "url": "=https://scrapercity.com/api/v1/scrape/status/{{ $('Store Run ID').item.json.runId }}",
        "method": "GET",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "11111111-0000-0000-0000-000000000014",
      "name": "Is Scrape Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        -250,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-001",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "11111111-0000-0000-0000-000000000015",
      "name": "Download Scraped Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        400
      ],
      "parameters": {
        "url": "=https://scrapercity.com/api/downloads/{{ $('Store Run ID').item.json.runId }}",
        "method": "GET",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "11111111-0000-0000-0000-000000000016",
      "name": "Parse CSV and Format Leads",
      "type": "n8n-nodes-base.code",
      "position": [
        250,
        400
      ],
      "parameters": {
        "jsCode": "// Parse CSV text into structured lead objects\n// Expects standard Apollo export column headers\nconst csvText = $input.first().json.data;\n\nif (!csvText || typeof csvText !== 'string') {\n  return [];\n}\n\nconst lines = csvText.split('\\n').filter(l => l.trim().length > 0);\n\nif (lines.length < 2) {\n  return [];\n}\n\n// Parse header row -- handle quoted fields\nconst parseRow = (line) => {\n  const result = [];\n  let current = '';\n  let insideQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const ch = line[i];\n    if (ch === '\"') {\n      insideQuotes = !insideQuotes;\n    } else if (ch === ',' && !insideQuotes) {\n      result.push(current.trim());\n      current = '';\n    } else {\n      current += ch;\n    }\n  }\n  result.push(current.trim());\n  return result;\n};\n\nconst headers = parseRow(lines[0]);\nconst items = [];\n\nfor (let i = 1; i < lines.length; i++) {\n  const values = parseRow(lines[i]);\n  if (values.length < 2) continue;\n  const lead = {};\n  headers.forEach((header, idx) => {\n    lead[header] = values[idx] || '';\n  });\n  items.push({ json: lead });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "11111111-0000-0000-0000-000000000017",
      "name": "Remove Duplicate Leads",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        500,
        400
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "Email"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "11111111-0000-0000-0000-000000000018",
      "name": "Save Leads to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        750,
        400
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {
          "valueInputMode": "USER_ENTERED"
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Store Run ID": {
      "main": [
        [
          {
            "node": "Polling Loop Controller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 60 Seconds": {
      "main": [
        [
          {
            "node": "Check Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Scrape Status": {
      "main": [
        [
          {
            "node": "Is Scrape Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Scrape Complete?": {
      "main": [
        [
          {
            "node": "Download Scraped Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Polling Loop Controller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Leads": {
      "main": [
        [
          {
            "node": "Save Leads to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Polling Loop Controller": {
      "main": [
        [
          {
            "node": "Wait 60 Seconds",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Download Scraped Results": {
      "main": [
        [
          {
            "node": "Parse CSV and Format Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Apollo Lead Scrape": {
      "main": [
        [
          {
            "node": "Store Run ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse CSV and Format Leads": {
      "main": [
        [
          {
            "node": "Remove Duplicate Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Search Parameters": {
      "main": [
        [
          {
            "node": "Start Apollo Lead Scrape",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Configure Search Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}