This workflow corresponds to n8n.io template #14078 — we link there as the canonical source.
This workflow follows the Airtable → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"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
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
airtableTokenApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is built for real estate investors, wholesalers, and skip tracers who need to find contact details -- phone numbers, emails, and addresses -- for property owners at scale. It automates the entire lookup process using the ScraperCity People Finder API and stores…
Source: https://n8n.io/workflows/14078/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Customer service or support teams who want to use their Zendesk articles in other tools. Content/Knowledge managers consolidating or migrating knowledge bases. Ops/automation specialists who want Mark
This template uses the Sleeper API to fetch the complete list of NFL players and stores them in an Airtable base. It’s built to run daily and ensures you have the most up-to-date player list for fanta
This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod
Sync your Google Contacts with your Notion database.
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.