{
  "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": "names",
              "type": "string",
              "value": "John Smith,Jane Doe"
            },
            {
              "id": "b1000002",
              "name": "phones",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000003",
              "name": "emails",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000004",
              "name": "max_results",
              "type": "number",
              "value": 1
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000003",
      "name": "Build Request Body",
      "type": "n8n-nodes-base.code",
      "position": [
        -1500,
        400
      ],
      "parameters": {
        "jsCode": "// Split comma-separated inputs into arrays, filtering empty strings\nconst toArr = val => val ? val.split(',').map(s => s.trim()).filter(Boolean) : [];\n\nconst cfg = $input.first().json;\n\nreturn [{\n  json: {\n    name: toArr(cfg.names),\n    email: toArr(cfg.emails),\n    phone_number: toArr(cfg.phones),\n    street_citystatezip: [],\n    max_results: cfg.max_results || 1\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000004",
      "name": "Submit Skip Trace Job",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1250,
        400
      ],
      "parameters": {
        "url": "https://app.scrapercity.com/api/v1/scrape/people-finder",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000005",
      "name": "Store Run ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -1000,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b2000001",
              "name": "runId",
              "type": "string",
              "value": "={{ $json.runId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000006",
      "name": "Polling Loop",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -750,
        400
      ],
      "parameters": {
        "options": {
          "reset": false
        },
        "batchSize": 1
      },
      "typeVersion": 3,
      "alwaysOutputData": false
    },
    {
      "id": "a1000000-0000-0000-0000-000000000007",
      "name": "Wait 60 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        -500,
        400
      ],
      "parameters": {
        "amount": 60
      },
      "typeVersion": 1.1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000008",
      "name": "Check Scrape Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -250,
        400
      ],
      "parameters": {
        "url": "=https://app.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": "a1000000-0000-0000-0000-000000000009",
      "name": "Is Scrape Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        0,
        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-000000000010",
      "name": "Download Results CSV",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        250,
        400
      ],
      "parameters": {
        "url": "=https://app.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": "a1000000-0000-0000-0000-000000000011",
      "name": "Parse and Format CSV Results",
      "type": "n8n-nodes-base.code",
      "position": [
        500,
        400
      ],
      "parameters": {
        "jsCode": "// Parse CSV text into structured records\nconst csvText = $input.first().json.data || $input.first().json.body || '';\n\nif (!csvText || typeof csvText !== 'string') {\n  return [{ json: { error: 'No CSV data returned', runId: $('Store Run ID').item.json.runId } }];\n}\n\nconst lines = csvText.trim().split('\\n');\nif (lines.length < 2) {\n  return [{ json: { error: 'Empty CSV', runId: $('Store Run ID').item.json.runId } }];\n}\n\n// Parse header row, handling quoted fields\nconst parseRow = row => {\n  const result = [];\n  let inQuotes = false;\n  let current = '';\n  for (let i = 0; i < row.length; i++) {\n    const ch = row[i];\n    if (ch === '\"') {\n      inQuotes = !inQuotes;\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 = parseRow(lines[0]);\nconst records = [];\nconst seenKeys = new Set();\n\nfor (let i = 1; i < lines.length; i++) {\n  const values = parseRow(lines[i]);\n  if (values.length < 2) continue;\n  const record = {};\n  headers.forEach((h, idx) => {\n    record[h] = values[idx] || '';\n  });\n  // Deduplicate by name + primary_phone combination\n  const dedupeKey = (record['name'] || '') + '|' + (record['primary_phone'] || record['phone'] || '');\n  if (!seenKeys.has(dedupeKey)) {\n    seenKeys.add(dedupeKey);\n    records.push({ json: record });\n  }\n}\n\nreturn records.length > 0 ? records : [{ json: { error: 'No records parsed', raw: csvText.substring(0, 200) } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000012",
      "name": "Remove Duplicate Contacts",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        750,
        400
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "name"
            },
            {
              "fieldName": "primary_phone"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000013",
      "name": "Write Results to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1000,
        400
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "appendOrUpdate",
        "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. You enter names, phone numbers, or emails in the Configure Search Inputs node.\n2. The workflow submits a skip trace job to the ScraperCity People Finder API.\n3. It polls for completion every 60 seconds (jobs take 10-60 min).\n4. Once done, it downloads the CSV, parses each contact, deduplicates, and writes rows 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 lookup targets.\n4. Set your Google Sheet ID in **Write 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": 980,
        "height": 330,
        "content": "## Configuration\nEnter comma-separated names, phones, or emails in **Configure Search Inputs**. **Build Request Body** converts them into arrays for the API."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000022",
      "name": "Section - Submit Job",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 980,
        "height": 330,
        "content": "## Submit Job\n**Submit Skip Trace Job** POSTs to the ScraperCity People Finder endpoint. **Store Run ID** saves the returned job ID for later polling."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000023",
      "name": "Section - Async Polling Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 980,
        "height": 330,
        "content": "## Async Polling Loop\n**Wait 60 Seconds** pauses between checks. **Check Scrape Status** hits the status endpoint. **Is Scrape Complete?** routes to download on success or loops back."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000024",
      "name": "Section - Download and Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 330,
        "content": "## Download and Output\n**Download Results CSV** fetches the file. **Parse and Format CSV Results** splits into records. **Remove Duplicate Contacts** dedupes. **Write Results to Google Sheets** appends rows."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Polling Loop": {
      "main": [
        [
          {
            "node": "Wait 60 Seconds",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Store Run ID": {
      "main": [
        [
          {
            "node": "Polling Loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 60 Seconds": {
      "main": [
        [
          {
            "node": "Check Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Request Body": {
      "main": [
        [
          {
            "node": "Submit Skip Trace Job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Scrape Status": {
      "main": [
        [
          {
            "node": "Is Scrape Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Scrape Complete?": {
      "main": [
        [
          {
            "node": "Download Results CSV",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Polling Loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Results CSV": {
      "main": [
        [
          {
            "node": "Parse and Format CSV Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Skip Trace Job": {
      "main": [
        [
          {
            "node": "Store Run ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Search Inputs": {
      "main": [
        [
          {
            "node": "Build Request Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Contacts": {
      "main": [
        [
          {
            "node": "Write Results to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Format CSV Results": {
      "main": [
        [
          {
            "node": "Remove Duplicate Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Configure Search Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}