{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "nodes": [
    {
      "id": "a1000000-0000-0000-0000-000000000001",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -2000,
        400
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000002",
      "name": "Configure Search Inputs",
      "type": "n8n-nodes-base.set",
      "position": [
        -1750,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b1000001",
              "name": "searchName",
              "type": "string",
              "value": "Jane Doe"
            },
            {
              "id": "b1000002",
              "name": "searchPhone",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000003",
              "name": "searchEmail",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000004",
              "name": "maxResults",
              "type": "number",
              "value": 3
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000003",
      "name": "Start People Finder Scrape",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1500,
        400
      ],
      "parameters": {
        "url": "https://app.scrapercity.com/api/v1/scrape/people-finder",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"name\": {{ $json.searchName ? '[\"' + $json.searchName + '\"]' : '[]' }},\n  \"email\": {{ $json.searchEmail ? '[\"' + $json.searchEmail + '\"]' : '[]' }},\n  \"phone_number\": {{ $json.searchPhone ? '[\"' + $json.searchPhone + '\"]' : '[]' }},\n  \"street_citystatezip\": [],\n  \"max_results\": {{ $json.maxResults }}\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000004",
      "name": "Store Run ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -1250,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b2000001",
              "name": "runId",
              "type": "string",
              "value": "={{ $json.runId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000005",
      "name": "Wait Before First Status Check",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1000,
        400
      ],
      "parameters": {
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000006",
      "name": "Poll Loop",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -750,
        400
      ],
      "parameters": {
        "options": {
          "maxIterations": 30
        }
      },
      "typeVersion": 3
    },
    {
      "id": "a1000000-0000-0000-0000-000000000007",
      "name": "Check Scrape Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -500,
        400
      ],
      "parameters": {
        "url": "=https://app.scrapercity.com/api/v1/scrape/status/{{ $json.runId }}",
        "method": "GET",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000008",
      "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": "c1000001",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000009",
      "name": "Wait 60 Seconds Before Retry",
      "type": "n8n-nodes-base.wait",
      "position": [
        -250,
        592
      ],
      "parameters": {
        "amount": 60
      },
      "typeVersion": 1.1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000010",
      "name": "Download Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        400
      ],
      "parameters": {
        "url": "=https://app.scrapercity.com/api/downloads/{{ $json.runId }}",
        "method": "GET",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000011",
      "name": "Parse CSV Results",
      "type": "n8n-nodes-base.code",
      "position": [
        250,
        400
      ],
      "parameters": {
        "jsCode": "// Parse CSV text returned from ScraperCity download endpoint\n// Handles quoted fields and normalises rows into key/value objects\n\nconst raw = items[0].json.data ?? items[0].json.body ?? items[0].json;\nconst csvText = typeof raw === 'string' ? raw : JSON.stringify(raw);\n\nif (!csvText || csvText.trim() === '') {\n  return [{ json: { error: 'No CSV data returned' } }];\n}\n\nconst lines = csvText.trim().split('\\n');\nif (lines.length < 2) {\n  return [{ json: { error: 'CSV has no data rows' } }];\n}\n\nfunction parseCsvLine(line) {\n  const result = [];\n  let current = '';\n  let inQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const ch = line[i];\n    if (ch === '\"') {\n      if (inQuotes && line[i + 1] === '\"') {\n        current += '\"';\n        i++;\n      } else {\n        inQuotes = !inQuotes;\n      }\n    } else if (ch === ',' && !inQuotes) {\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 = parseCsvLine(lines[0]);\nconst output = [];\n\nfor (let i = 1; i < lines.length; i++) {\n  if (!lines[i].trim()) continue;\n  const values = parseCsvLine(lines[i]);\n  const row = {};\n  headers.forEach((h, idx) => {\n    row[h] = values[idx] ?? '';\n  });\n  output.push({ json: row });\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000012",
      "name": "Remove Duplicate Contacts",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        500,
        400
      ],
      "parameters": {
        "options": {},
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "full_name"
            },
            {
              "fieldName": "address"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000013",
      "name": "Save Results to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        750,
        400
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "a1000000-0000-0000-0000-000000000020",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2600,
        100
      ],
      "parameters": {
        "width": 450,
        "height": 580,
        "content": "## How it works\n1. Enter a name, phone, or email in the Configure Search Inputs node.\n2. The workflow submits a people-finder job to the ScraperCity API.\n3. It polls for completion every 60 seconds (jobs take 2-20 min).\n4. Results are downloaded, parsed from CSV, deduped, and saved to Google Sheets.\n\n## Setup steps\n1. Create a Header Auth credential named **ScraperCity API Key** -- set header to `Authorization`, value to `Bearer YOUR_KEY`.\n2. Connect a Google Sheets OAuth2 credential.\n3. Edit **Configure Search Inputs** with your target person.\n4. Set your Google Sheet ID in **Save Results to Google Sheets**.\n5. Click Execute workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000021",
      "name": "Section - Configuration",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2040,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 330,
        "content": "## Configuration\nSet your search target in **Configure Search Inputs** -- name, phone, or email. Add your ScraperCity API key credential to the HTTP nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000022",
      "name": "Section - Submit and Store",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1290,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 330,
        "content": "## Submit Scrape Job\n**Start People Finder Scrape** POSTs to the API and returns a job ID. **Store Run ID** saves it for polling and download references."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000023",
      "name": "Section - Async Polling Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -540,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 530,
        "content": "## Async Polling Loop\n**Check Scrape Status** queries the job. **Is Scrape Complete?** routes to download on success. Otherwise **Wait 60 Seconds Before Retry** loops back through the poll."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000024",
      "name": "Section - Parse and Save",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        210,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 330,
        "content": "## Parse and Save Results\n**Download Results** fetches the CSV. **Parse CSV Results** converts to JSON records. **Remove Duplicate Contacts** dedupes. **Save Results to Google Sheets** writes each row."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Poll Loop": {
      "main": [
        [
          {
            "node": "Check Scrape Status",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Store Run ID": {
      "main": [
        [
          {
            "node": "Wait Before First Status Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Results": {
      "main": [
        [
          {
            "node": "Parse CSV Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse CSV Results": {
      "main": [
        [
          {
            "node": "Remove Duplicate Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Scrape Status": {
      "main": [
        [
          {
            "node": "Is Scrape Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Scrape Complete?": {
      "main": [
        [
          {
            "node": "Download Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 60 Seconds Before Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Search Inputs": {
      "main": [
        [
          {
            "node": "Start People Finder Scrape",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Contacts": {
      "main": [
        [
          {
            "node": "Save Results to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start People Finder Scrape": {
      "main": [
        [
          {
            "node": "Store Run ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 60 Seconds Before Retry": {
      "main": [
        [
          {
            "node": "Poll Loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before First Status Check": {
      "main": [
        [
          {
            "node": "Poll Loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Configure Search Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}