This workflow corresponds to n8n.io template #14406 — 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
},
"name": "Scrape Apollo.io leads and sync enriched contacts into Airtable via ScraperCity",
"nodes": [
{
"id": "eca51c8c-dad6-472c-8af3-be7aca6a77bb",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
0
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Scrape Apollo.io leads and sync enriched contacts into Airtable via ScraperCity\n\n### How it works\n\n1. The workflow is triggered manually, and search parameters (job titles, industry, company size, lead count) are configured before initiating a scrape job on Apollo.io via the ScraperCity API.\n2. The scrape job ID is stored alongside Airtable configuration, then the workflow waits 60 seconds before polling ScraperCity for the job's completion status.\n3. If the scrape is not yet complete, the workflow waits another 60 seconds, re-attaches the run ID, and re-checks the status in a polling loop until the job finishes.\n4. Once complete, the raw lead results are downloaded from ScraperCity, then parsed and normalized into a consistent contact structure.\n5. Duplicate contacts are removed and only contacts with a valid email address are retained before the cleaned leads are saved to an Airtable base.\n\n### Setup steps\n\n- [ ] Create a ScraperCity account and obtain your API key for Apollo.io lead scraping\n- [ ] Set your desired search parameters (jobTitles, industry, companySize, leadCount) in the 'Configure Search Parameters' node\n- [ ] Update the 'Store Run ID' node with your Airtable Base ID and Table Name\n- [ ] Connect your Airtable account credentials in n8n and verify the 'Save Leads to Airtable' node points to the correct base and table\n- [ ] Ensure your Airtable table has columns matching the fields output by the 'Parse and Normalize Leads' node\n- [ ] Test the workflow by clicking 'Execute workflow' and monitoring the polling loop until leads appear in Airtable\n\n### Customization\n\nAdjust the two 60-second wait durations to suit expected scrape completion times \u2014 longer waits reduce API calls for large scrapes. Modify the filter in 'Filter Contacts with Email' to add additional quality criteria (e.g., phone number present, specific job title). The deduplication key in 'Remove Duplicate Contacts' can be changed from email to another unique field if needed."
},
"typeVersion": 1
},
{
"id": "c4735e11-870f-427d-8238-bb2e3ed74669",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
144
],
"parameters": {
"color": 7,
"width": 448,
"height": 320,
"content": "## Workflow trigger and setup\n\nManually triggers the workflow and defines the lead search criteria (job titles, industry, company size, lead count) before any API calls are made."
},
"typeVersion": 1
},
{
"id": "77ec30c6-3e32-4083-8487-14288d1671a8",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
128
],
"parameters": {
"color": 7,
"width": 448,
"height": 336,
"content": "## Initiate Apollo scrape job\n\nSends a POST request to ScraperCity to start the Apollo.io lead scrape, then stores the returned run ID alongside Airtable configuration for downstream use."
},
"typeVersion": 1
},
{
"id": "4f033aba-270d-4178-b847-df43c0fbb94c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
144
],
"parameters": {
"color": 7,
"width": 704,
"height": 320,
"content": "## Poll scrape completion status\n\nWaits 60 seconds, then polls ScraperCity to check whether the scrape job is finished. Branches on the result \u2014 proceeding to download on success or entering the retry loop on pending."
},
"typeVersion": 1
},
{
"id": "cc8aabbe-9fb9-43b8-8ecb-305d42e426fa",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1968,
368
],
"parameters": {
"color": 7,
"width": 464,
"height": 560,
"content": "## Retry loop for pending scrapes\n\nIf the scrape is not yet complete, waits another 60 seconds, re-attaches the run ID and Airtable metadata, then loops back to re-check the status."
},
"typeVersion": 1
},
{
"id": "0a3704b0-f51e-4815-99d0-34ee01c437e6",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1968,
0
],
"parameters": {
"color": 7,
"width": 464,
"height": 336,
"content": "## Download and normalize leads\n\nDownloads the completed lead results from ScraperCity and runs a code node to parse and normalize the raw contact objects into a consistent, usable structure."
},
"typeVersion": 1
},
{
"id": "c7704576-b166-459b-87c5-854aeba7e8c7",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
144
],
"parameters": {
"color": 7,
"width": 704,
"height": 320,
"content": "## Clean and save to Airtable\n\nRemoves duplicate contacts, filters out any records missing an email address, and writes the final clean lead list to the configured Airtable base and table."
},
"typeVersion": 1
},
{
"id": "a1000000-0000-0000-0000-000000000001",
"name": "When clicking 'Execute workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
208,
304
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a1000000-0000-0000-0000-000000000002",
"name": "Configure Search Parameters",
"type": "n8n-nodes-base.set",
"position": [
464,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b1000001",
"name": "jobTitles",
"type": "string",
"value": "CEO,CTO,VP of Sales"
},
{
"id": "b1000002",
"name": "industry",
"type": "string",
"value": "Technology"
},
{
"id": "b1000003",
"name": "companySize",
"type": "string",
"value": "11-50"
},
{
"id": "b1000004",
"name": "leadCount",
"type": "number",
"value": 100
},
{
"id": "b1000005",
"name": "airtableBaseId",
"type": "string",
"value": "appYOUR_BASE_ID"
},
{
"id": "b1000006",
"name": "airtableTableName",
"type": "string",
"value": "Leads"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000003",
"name": "Start Apollo Lead Scrape",
"type": "n8n-nodes-base.httpRequest",
"position": [
720,
304
],
"parameters": {
"url": "https://app.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\": \"Apollo Export {{ $now.toISO() }}\"\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": [
976,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c1000001",
"name": "runId",
"type": "string",
"value": "={{ $json.runId }}"
},
{
"id": "c1000002",
"name": "airtableBaseId",
"type": "string",
"value": "={{ $('Configure Search Parameters').item.json.airtableBaseId }}"
},
{
"id": "c1000003",
"name": "airtableTableName",
"type": "string",
"value": "={{ $('Configure Search Parameters').item.json.airtableTableName }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000005",
"name": "Wait 60 Seconds Before Poll",
"type": "n8n-nodes-base.wait",
"position": [
1248,
304
],
"parameters": {
"unit": "seconds",
"amount": 60
},
"typeVersion": 1.1
},
{
"id": "a1000000-0000-0000-0000-000000000006",
"name": "Check Scrape Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
1504,
304
],
"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": [
1760,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d1000001",
"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": [
2016,
768
],
"parameters": {
"unit": "seconds",
"amount": 60
},
"typeVersion": 1.1
},
{
"id": "a1000000-0000-0000-0000-000000000009",
"name": "Preserve Run ID on Retry",
"type": "n8n-nodes-base.set",
"position": [
2288,
528
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e1000001",
"name": "runId",
"type": "string",
"value": "={{ $json.runId }}"
},
{
"id": "e1000002",
"name": "airtableBaseId",
"type": "string",
"value": "={{ $('Configure Search Parameters').item.json.airtableBaseId }}"
},
{
"id": "e1000003",
"name": "airtableTableName",
"type": "string",
"value": "={{ $('Configure Search Parameters').item.json.airtableTableName }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a1000000-0000-0000-0000-000000000010",
"name": "Download Lead Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
2016,
176
],
"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 and Normalize Leads",
"type": "n8n-nodes-base.code",
"position": [
2288,
176
],
"parameters": {
"jsCode": "// ScraperCity returns an array of contact objects.\n// Normalize each lead into a flat structure for Airtable.\nconst raw = Array.isArray($input.first().json) ? $input.first().json : ($input.first().json.contacts || $input.first().json.data || []);\n\nconst results = [];\nfor (const contact of raw) {\n results.push({\n json: {\n firstName: contact.first_name || contact.firstName || '',\n lastName: contact.last_name || contact.lastName || '',\n fullName: contact.name || `${contact.first_name || ''} ${contact.last_name || ''}`.trim(),\n jobTitle: contact.title || contact.job_title || '',\n company: contact.organization_name || contact.company || '',\n workEmail: contact.email || contact.work_email || '',\n personalEmail: contact.personal_email || '',\n phone: contact.phone || contact.primary_phone || '',\n linkedinUrl: contact.linkedin_url || '',\n city: contact.city || '',\n country: contact.country || '',\n companyWebsite: contact.account ? (contact.account.website_url || '') : (contact.website_url || '')\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "a1000000-0000-0000-0000-000000000012",
"name": "Remove Duplicate Contacts",
"type": "n8n-nodes-base.removeDuplicates",
"position": [
2544,
304
],
"parameters": {
"compare": "selectedFields",
"options": {},
"fieldsToCompare": {
"fields": [
{
"fieldName": "workEmail"
}
]
}
},
"typeVersion": 2
},
{
"id": "a1000000-0000-0000-0000-000000000013",
"name": "Filter Contacts with Email",
"type": "n8n-nodes-base.filter",
"position": [
2800,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "f1000001",
"operator": {
"type": "string",
"operation": "isNotEmpty"
},
"leftValue": "={{ $json.workEmail }}",
"rightValue": ""
},
{
"id": "f1000002",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.workEmail }}",
"rightValue": "@"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a1000000-0000-0000-0000-000000000014",
"name": "Save Leads to Airtable",
"type": "n8n-nodes-base.airtable",
"position": [
3056,
304
],
"parameters": {
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $('Store Run ID').item.json.airtableBaseId }}"
},
"table": {
"__rl": true,
"mode": "name",
"value": "={{ $('Store Run ID').item.json.airtableTableName }}"
},
"columns": {
"value": {
"City": "={{ $json.city }}",
"Phone": "={{ $json.phone }}",
"Company": "={{ $json.company }}",
"Country": "={{ $json.country }}",
"Full Name": "={{ $json.fullName }}",
"Job Title": "={{ $json.jobTitle }}",
"Last Name": "={{ $json.lastName }}",
"First Name": "={{ $json.firstName }}",
"Work Email": "={{ $json.workEmail }}",
"LinkedIn URL": "={{ $json.linkedinUrl }}",
"Personal Email": "={{ $json.personalEmail }}",
"Company Website": "={{ $json.companyWebsite }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.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 Lead Results",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 60 Seconds Before Retry",
"type": "main",
"index": 0
}
]
]
},
"Download Lead Results": {
"main": [
[
{
"node": "Parse and Normalize Leads",
"type": "main",
"index": 0
}
]
]
},
"Preserve Run ID on Retry": {
"main": [
[
{
"node": "Check Scrape Status",
"type": "main",
"index": 0
}
]
]
},
"Start Apollo Lead Scrape": {
"main": [
[
{
"node": "Store Run ID",
"type": "main",
"index": 0
}
]
]
},
"Parse and Normalize Leads": {
"main": [
[
{
"node": "Remove Duplicate Contacts",
"type": "main",
"index": 0
}
]
]
},
"Remove Duplicate Contacts": {
"main": [
[
{
"node": "Filter Contacts with Email",
"type": "main",
"index": 0
}
]
]
},
"Filter Contacts with Email": {
"main": [
[
{
"node": "Save Leads to Airtable",
"type": "main",
"index": 0
}
]
]
},
"Configure Search Parameters": {
"main": [
[
{
"node": "Start Apollo Lead Scrape",
"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": "Preserve Run ID on Retry",
"type": "main",
"index": 0
}
]
]
},
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "Configure Search Parameters",
"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
Sales teams, lead generation agencies, and growth marketers who want to pull targeted B2B contacts from Apollo.io, enrich them with verified emails and mobile numbers, and push clean records directly into an Airtable CRM -- without manual data entry or expensive Apollo…
Source: https://n8n.io/workflows/14406/ — 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.
This template is for sales teams, lead generation agencies, and growth marketers who want to build targeted lists of WooCommerce store owners -- complete with verified emails, phone numbers, and socia
Why spend $1,000s on lead gen when your perfect leads are already waiting in Apollo? You’ve already filtered the ideal prospects. You know who they are, where they work, and what they do.
Automatically identify and qualify ideal customer prospects from LinkedIn post reactions using AI-powered profile analysis and intelligent data enrichment.
This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em
This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese