{
  "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": "ownerName",
              "type": "string",
              "value": "John Smith"
            },
            {
              "id": "b1000002",
              "name": "ownerPhone",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000003",
              "name": "ownerEmail",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000004",
              "name": "streetCityStateZip",
              "type": "string",
              "value": ""
            },
            {
              "id": "b1000005",
              "name": "maxResults",
              "type": "number",
              "value": 3
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000003",
      "name": "Submit Skip Trace Job",
      "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.ownerName ? '[\"' + $json.ownerName + '\"]' : '[]' }},\n  \"email\": {{ $json.ownerEmail ? '[\"' + $json.ownerEmail + '\"]' : '[]' }},\n  \"phone_number\": {{ $json.ownerPhone ? '[\"' + $json.ownerPhone + '\"]' : '[]' }},\n  \"street_citystatezip\": {{ $json.streetCityStateZip ? '[\"' + $json.streetCityStateZip + '\"]' : '[]' }},\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 60 Seconds Before Poll",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1000,
        400
      ],
      "parameters": {
        "amount": 60
      },
      "typeVersion": 1.1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000006",
      "name": "Check Scrape Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -750,
        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-000000000007",
      "name": "Is Scrape Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        -500,
        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-000000000008",
      "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-000000000009",
      "name": "Pass Run ID to Retry",
      "type": "n8n-nodes-base.set",
      "position": [
        -500,
        592
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b3000001",
              "name": "runId",
              "type": "string",
              "value": "={{ $json.runId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000000-0000-0000-0000-000000000010",
      "name": "Download Trace Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -250,
        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 Contact Records",
      "type": "n8n-nodes-base.code",
      "position": [
        0,
        400
      ],
      "parameters": {
        "jsCode": "// Parse CSV or JSON array returned from ScraperCity download\n// The API may return a JSON array or a CSV string depending on job type.\n\nconst raw = items[0].json;\n\n// If the response body is already a parsed array, use it directly\nlet records = [];\n\nif (Array.isArray(raw)) {\n  records = raw;\n} else if (typeof raw === 'object' && raw.data && Array.isArray(raw.data)) {\n  records = raw.data;\n} else if (typeof raw === 'string') {\n  // Simple CSV parse: first line = headers\n  const lines = raw.trim().split('\\n');\n  const headers = lines[0].split(',').map(h => h.trim().replace(/\"/g, ''));\n  for (let i = 1; i < lines.length; i++) {\n    const values = lines[i].split(',').map(v => v.trim().replace(/\"/g, ''));\n    const row = {};\n    headers.forEach((h, idx) => { row[h] = values[idx] || ''; });\n    records.push(row);\n  }\n} else {\n  // Fallback: treat the whole response as one record\n  records = [raw];\n}\n\n// Normalize field names to a consistent schema\nreturn records.map(r => ({\n  json: {\n    full_name: r.full_name || r.name || r.Name || '',\n    phone: r.phone || r.phone_number || r.primary_phone || r.Phone || '',\n    email: r.email || r.Email || '',\n    address: r.address || r.current_address || r.Address || '',\n    city: r.city || r.City || '',\n    state: r.state || r.State || '',\n    zip: r.zip || r.zip_code || r.Zip || '',\n    age: r.age || r.Age || '',\n    raw_source: JSON.stringify(r)\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000012",
      "name": "Remove Duplicate Contacts",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        250,
        400
      ],
      "parameters": {
        "options": {},
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "full_name"
            },
            {
              "fieldName": "phone"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000013",
      "name": "Filter Records with Contact Info",
      "type": "n8n-nodes-base.filter",
      "position": [
        500,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "d1000001",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              },
              "leftValue": "={{ $json.phone }}",
              "rightValue": ""
            },
            {
              "id": "d1000002",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              },
              "leftValue": "={{ $json.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a1000000-0000-0000-0000-000000000014",
      "name": "Sync Contacts to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        750,
        400
      ],
      "parameters": {
        "baseId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "columns": {
          "value": {
            "Age": "={{ $json.age }}",
            "ZIP": "={{ $json.zip }}",
            "City": "={{ $json.city }}",
            "Email": "={{ $json.email }}",
            "Phone": "={{ $json.phone }}",
            "State": "={{ $json.state }}",
            "Address": "={{ $json.address }}",
            "Full Name": "={{ $json.full_name }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "tableId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "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. **Configure Search Inputs** -- set the property owner name, phone, or email you want to trace.\n2. **Submit Skip Trace Job** posts the lookup to ScraperCity People Finder and receives a `runId`.\n3. The polling loop waits 60 seconds, then checks job status via **Check Scrape Status** until `SUCCEEDED`.\n4. **Download Trace Results** fetches the completed data, which **Parse Contact Records** normalises into clean fields.\n5. Duplicates are removed and records with at least one contact detail are synced to Airtable.\n\n## Setup steps\n1. In n8n Credentials, create an **HTTP Header Auth** credential named `ScraperCity API Key` -- header name `Authorization`, value `Bearer YOUR_KEY`.\n2. Open **Configure Search Inputs** and enter the owner name (or phone/email) you want to look up.\n3. In **Sync Contacts to Airtable**, connect your Airtable credential and set the correct Base ID and Table ID.\n4. Make sure your Airtable table has columns: Full Name, Phone, Email, Address, City, State, ZIP, Age."
      },
      "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\n**Configure Search Inputs** is the only node you need to edit before running. Set `ownerName`, `ownerPhone`, or `ownerEmail` -- at least one is required. Adjust `maxResults` (default 3) to control how many contact matches ScraperCity returns per person."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000022",
      "name": "Section - Job Submission",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 530,
        "content": "## Job Submission\n**Submit Skip Trace Job** sends the lookup payload to the ScraperCity People Finder endpoint and receives a `runId`. **Store Run ID** saves that ID so the polling loop can reference it throughout execution."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000023",
      "name": "Section - Async Polling Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -290,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 730,
        "height": 530,
        "content": "## Async Polling Loop\nScraperCity jobs run asynchronously and may take several minutes. **Wait 60 Seconds Before Poll** pauses execution. **Check Scrape Status** queries the job. **Is Scrape Complete?** routes to download on success. Otherwise **Pass Run ID to Retry** feeds the ID into **Wait 60 Seconds Before Retry**, which loops back to the status check."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000000-0000-0000-0000-000000000024",
      "name": "Section - Results Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        460,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 330,
        "content": "## Results Processing\n**Download Trace Results** fetches the completed CSV/JSON payload. **Parse Contact Records** normalises fields into a consistent schema (name, phone, email, address). **Remove Duplicate Contacts** deduplicates by name and phone. **Filter Records with Contact Info** drops rows with no usable contact detail before writing to Airtable."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Store Run ID": {
      "main": [
        [
          {
            "node": "Wait 60 Seconds Before Poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Scrape Status": {
      "main": [
        [
          {
            "node": "Is Scrape Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Scrape Complete?": {
      "main": [
        [
          {
            "node": "Download Trace Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Pass Run ID to Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pass Run ID to Retry": {
      "main": [
        [
          {
            "node": "Wait 60 Seconds Before Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Contact Records": {
      "main": [
        [
          {
            "node": "Remove Duplicate Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Skip Trace Job": {
      "main": [
        [
          {
            "node": "Store Run ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Trace Results": {
      "main": [
        [
          {
            "node": "Parse Contact Records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Search Inputs": {
      "main": [
        [
          {
            "node": "Submit Skip Trace Job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Contacts": {
      "main": [
        [
          {
            "node": "Filter Records with Contact Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 60 Seconds Before Poll": {
      "main": [
        [
          {
            "node": "Check Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 60 Seconds Before Retry": {
      "main": [
        [
          {
            "node": "Check Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Records with Contact Info": {
      "main": [
        [
          {
            "node": "Sync Contacts to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Configure Search Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}