Batch-enrich Salesforce contacts with Apollo using an agent skill
low complexityCost: Usage-based
Prerequisites
Prerequisites
- Claude Code or another agent that supports the Agent Skills standard
- Salesforce instance URL stored as
SALESFORCE_INSTANCE_URLenvironment variable - Salesforce access token stored as
SALESFORCE_ACCESS_TOKENenvironment variable - Apollo API key stored as
APOLLO_API_KEYenvironment variable
Overview
This agent skill queries Salesforce for leads or contacts missing key enrichment fields (title, phone, industry), enriches them in batch via Apollo's People Enrich API, and writes the results back to Salesforce. Unlike the n8n approach which triggers on new records, this handles backfill — enriching existing records that were never enriched.
Step 1: Create the skill
Create .claude/skills/sf-apollo-enrich/SKILL.md:
---
name: sf-apollo-enrich
description: Batch-enrich un-enriched Salesforce leads with Apollo contact data
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Find Salesforce Leads missing key fields and enrich them with Apollo data.
Run: `python $SKILL_DIR/scripts/enrich.py`Step 2: Write the script
Create .claude/skills/sf-apollo-enrich/scripts/enrich.py:
#!/usr/bin/env python3
import os, sys, time, requests
INSTANCE_URL = os.environ.get("SALESFORCE_INSTANCE_URL")
ACCESS_TOKEN = os.environ.get("SALESFORCE_ACCESS_TOKEN")
APOLLO_KEY = os.environ.get("APOLLO_API_KEY")
if not all([INSTANCE_URL, ACCESS_TOKEN, APOLLO_KEY]):
print("ERROR: Set SALESFORCE_INSTANCE_URL, SALESFORCE_ACCESS_TOKEN, APOLLO_API_KEY")
sys.exit(1)
SF_HEADERS = {"Authorization": f"Bearer {ACCESS_TOKEN}", "Content-Type": "application/json"}
APOLLO_HEADERS = {"x-api-key": APOLLO_KEY, "Content-Type": "application/json"}
# Find un-enriched leads
soql = (
"SELECT Id, Email, FirstName, LastName, Company "
"FROM Lead "
"WHERE Title = null AND Email != null "
"ORDER BY CreatedDate DESC LIMIT 50"
)
resp = requests.get(
f"{INSTANCE_URL}/services/data/v59.0/query",
headers=SF_HEADERS,
params={"q": soql},
)
resp.raise_for_status()
leads = resp.json().get("records", [])
if not leads:
print("No un-enriched leads found")
sys.exit(0)
print(f"Found {len(leads)} un-enriched leads. Enriching...")
enriched = 0
skipped = 0
for lead in leads:
email = lead.get("Email")
if not email:
skipped += 1
continue
# Call Apollo People Enrich
try:
apollo_resp = requests.post(
"https://api.apollo.io/v1/people/enrich",
headers=APOLLO_HEADERS,
json={"email": email},
)
if apollo_resp.status_code == 429:
print("Rate limited — waiting 60 seconds")
time.sleep(60)
apollo_resp = requests.post(
"https://api.apollo.io/v1/people/enrich",
headers=APOLLO_HEADERS,
json={"email": email},
)
apollo_resp.raise_for_status()
person = apollo_resp.json().get("person")
if not person:
skipped += 1
continue
# Build update payload
update = {}
if person.get("title"):
update["Title"] = person["title"]
if person.get("linkedin_url"):
update["LinkedIn_URL__c"] = person["linkedin_url"]
if person.get("phone_numbers"):
phone = person["phone_numbers"][0].get("sanitized_number")
if phone:
update["Phone"] = phone
if person.get("departments"):
update["Department"] = person["departments"][0]
if person.get("organization", {}).get("name"):
update["Company"] = person["organization"]["name"]
if not update:
skipped += 1
continue
# Update Salesforce Lead
sf_resp = requests.patch(
f"{INSTANCE_URL}/services/data/v59.0/sobjects/Lead/{lead['Id']}",
headers=SF_HEADERS,
json=update,
)
sf_resp.raise_for_status()
enriched += 1
print(f" ✓ {lead['FirstName']} {lead['LastName']} — {person.get('title', 'no title')}")
except requests.exceptions.RequestException as e:
print(f" ✗ {lead['FirstName']} {lead['LastName']} — {e}")
skipped += 1
# Respect Apollo rate limit (100/min)
time.sleep(0.7)
print(f"\nDone. Enriched {enriched}/{len(leads)} leads. Skipped {skipped}.")Step 3: Run it
/sf-apollo-enrichA successful run looks like:
Found 50 un-enriched leads. Enriching...
✓ Sarah Chen — VP of Engineering
✓ Mike Johnson — Director of Sales
✓ Lisa Park — Head of Marketing
✗ David Kim — 404 Not Found
...
Done. Enriched 47/50 leads. Skipped 3.Step 4: Schedule (optional)
For daily enrichment of new un-enriched leads:
# crontab — run every day at 9 AM
0 9 * * * cd /path/to/project && python .claude/skills/sf-apollo-enrich/scripts/enrich.pyWhen to use this approach
- You need to backfill existing leads that were never enriched
- You want to run enrichment on demand before a sales blitz or campaign
- You prefer a script you can modify and version control
- You don't want to maintain n8n workflows or Salesforce Connected Apps for n8n
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.