Batch-enrich Salesforce contacts with Apollo using a Claude Code skill

low complexityCost: Usage-based
Compatible agents

This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.

Prerequisites

Prerequisites
  • Claude Code or another AI coding agent installed
  • Salesforce instance with API access enabled
  • Apollo.io account with API access (Basic plan or above)
Environment Variables
# Your Salesforce instance URL (e.g. https://yourorg.my.salesforce.com)
SALESFORCE_INSTANCE_URL=your_value_here
# Salesforce OAuth access token or session token with Lead read/write permissions
SALESFORCE_ACCESS_TOKEN=your_value_here
# Apollo.io API key for people enrichment
APOLLO_API_KEY=your_value_here

Why a Claude Code skill?

An Claude Code skill turns Salesforce enrichment into a conversation. Instead of configuring Connected Apps or maintaining n8n workflows, you tell the agent what you need:

  • "Enrich the 50 most recent leads missing a job title"
  • "Enrich leads from this week's webinar import"
  • "How many leads are missing phone numbers?"
  • "Re-enrich leads where Title is blank and Company is Acme"

The agent reads API reference files, builds the right SOQL query, calls Apollo, and updates Salesforce — all without you writing code. Unlike the n8n approach, this handles backfill of existing records naturally.

How it works

The skill has three parts:

  1. SKILL.md — instructions telling the agent the workflow steps and which reference files to consult
  2. references/ — API documentation for Salesforce SOQL queries, sObject PATCH, and Apollo People Enrich
  3. templates/ — field mapping rules defining which Apollo fields map to which Salesforce fields

When you invoke the skill, the agent reads these files, writes a script that follows the patterns, executes it, and reports the results.

What is a Claude Code skill?

An Claude Code skill is a directory of reference files and instructions that teach an AI coding agent how to complete a specific task. Unlike traditional automation that runs the same code every time, a skill lets the agent adapt — it can modify SOQL filters, change batch sizes, handle errors, and explain what it did, all based on your natural-language request. The agent generates and runs code on the fly using the API patterns in the reference files.

Step 1: Create the skill directory

mkdir -p .claude/skills/sf-apollo-enrich/{templates,references}

Step 2: Write the SKILL.md file

Create .claude/skills/sf-apollo-enrich/SKILL.md:

---
name: sf-apollo-enrich
description: Batch-enrich un-enriched Salesforce leads with Apollo contact data. Queries leads missing key fields, enriches via Apollo People Enrich API, and writes results back to Salesforce.
disable-model-invocation: true
allowed-tools: Bash, Read
---
 
## Workflow
 
1. Read `references/salesforce-api.md` for SOQL query and sObject update patterns
2. Read `references/apollo-people-enrich.md` for the enrichment API
3. Read `templates/field-mapping.md` for Apollo → Salesforce field mapping
4. Query Salesforce for Leads where Title is null and Email is not null (SOQL)
5. For each lead, call Apollo People Enrich with the email
6. Build an update payload using only fields that Apollo returned
7. PATCH each Lead in Salesforce with the enriched data
8. Print a summary: total found, enriched, skipped
 
## Rules
 
- Only update fields that Apollo returns non-null values for
- Rate limit: 0.7 seconds between Apollo calls (100/min limit)
- Handle Apollo 429 responses by waiting 60 seconds and retrying
- Filter out personal email domains before calling Apollo
- Use environment variables: SALESFORCE_INSTANCE_URL, SALESFORCE_ACCESS_TOKEN, APOLLO_API_KEY

Step 3: Add reference and template files

Create references/salesforce-api.md:

# Salesforce REST API Reference
 
## Query records with SOQL
 
```
GET {SALESFORCE_INSTANCE_URL}/services/data/v59.0/query?q={SOQL}
Authorization: Bearer {SALESFORCE_ACCESS_TOKEN}
```
 
Example SOQL — find Leads missing job title:
```sql
SELECT Id, Email, FirstName, LastName, Company, Title, Phone
FROM Lead
WHERE Title = null AND Email != null
ORDER BY CreatedDate DESC
LIMIT 50
```
 
Response:
```json
{
  "totalSize": 42,
  "records": [
    {
      "Id": "00Q5e000001abc",
      "Email": "jane@acme.com",
      "FirstName": "Jane",
      "LastName": "Smith",
      "Company": "Acme Inc",
      "Title": null,
      "Phone": null
    }
  ]
}
```
 
## Update a Lead
 
```
PATCH {SALESFORCE_INSTANCE_URL}/services/data/v59.0/sobjects/Lead/{LeadId}
Authorization: Bearer {SALESFORCE_ACCESS_TOKEN}
Content-Type: application/json
```
 
Request body:
```json
{
  "Title": "VP of Engineering",
  "Phone": "+14155550142",
  "Department": "Engineering",
  "LinkedIn_URL__c": "https://linkedin.com/in/janesmith"
}
```
 
Successful response: HTTP 204 No Content.
 
Notes:
- LinkedIn_URL__c is a custom field — must be created in Salesforce first
- Access token expires per session timeout settings. Use refresh tokens for long-running automations
- SOQL strings must be URL-encoded when passed as query params

Create references/apollo-people-enrich.md:

# Apollo People Enrich API Reference
 
```
POST https://api.apollo.io/v1/people/enrich
x-api-key: {APOLLO_API_KEY}
Content-Type: application/json
```
 
Request body:
```json
{
  "email": "jane@acme.com",
  "reveal_personal_emails": false
}
```
 
Response:
```json
{
  "person": {
    "title": "VP of Engineering",
    "seniority": "vp",
    "departments": ["engineering"],
    "linkedin_url": "https://linkedin.com/in/janesmith",
    "phone_numbers": [
      { "sanitized_number": "+14155550142", "type": "work_direct" }
    ],
    "organization": {
      "name": "Acme Inc",
      "estimated_num_employees": 500,
      "industry": "Computer Software",
      "annual_revenue": 50000000
    }
  }
}
```
 
If no match: `{"person": null}`. Costs 1 credit per call.
 
Rate limit: ~100 requests per minute. On 429 response, wait 60 seconds and retry.
 
Bulk alternative: POST /v1/people/bulk_enrich (up to 10 records per call).

Create templates/field-mapping.md:

# Field Mapping: Apollo → Salesforce
 
## Lead fields
 
| Apollo field | Salesforce field | Type | Notes |
|---|---|---|---|
| person.title | Title | Standard | Job title |
| person.phone_numbers[0].sanitized_number | Phone | Standard | First work phone |
| person.departments[0] | Department | Standard | Primary department |
| person.linkedin_url | LinkedIn_URL__c | Custom (URL) | Must create in Salesforce first |
| person.organization.name | Company | Standard | Lead company name |
| person.seniority | Seniority__c | Custom (Text) | Optional custom field |
 
## Account fields (optional, if updating parent Account)
 
| Apollo field | Salesforce field | Notes |
|---|---|---|
| person.organization.industry | Industry | Standard picklist |
| person.organization.estimated_num_employees | NumberOfEmployees | Standard number field |
 
## Rules
 
1. Only include fields in the update payload if Apollo returned a non-null value
2. Do not overwrite existing Salesforce values unless explicitly asked
3. Custom fields (LinkedIn_URL__c, Seniority__c) must be created in Salesforce before use

Step 4: Test the skill

# In Claude Code
/sf-apollo-enrich

Start with a small batch:

"Enrich 5 leads missing a title — show me what Apollo returns before updating"

The agent will query Salesforce, call Apollo, and display the proposed updates for your approval.

Step 5: Schedule it (optional)

Option A: Cron + CLI

# Run daily at 9 AM
0 9 * * * cd /path/to/project && claude -p "Run /sf-apollo-enrich for the 50 most recent leads missing a title." --allowedTools 'Bash,Read' 2>&1 >> /var/log/sf-enrich.log

Option B: GitHub Actions

name: Salesforce Apollo Enrichment
on:
  schedule:
    - cron: '0 14 * * *'  # 9 AM ET = 2 PM UTC
  workflow_dispatch: {}
jobs:
  enrich:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          prompt: "Run /sf-apollo-enrich for the 50 most recent leads missing a title."
          allowed_tools: "Bash,Read"
        env:
          SALESFORCE_INSTANCE_URL: ${{ secrets.SALESFORCE_INSTANCE_URL }}
          SALESFORCE_ACCESS_TOKEN: ${{ secrets.SALESFORCE_ACCESS_TOKEN }}
          APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}

Option C: Cowork Scheduled Tasks

Claude Desktop's Cowork supports built-in scheduled tasks. Open a Cowork session, type /schedule, and configure the cadence — hourly, daily, weekly, or weekdays only. Each scheduled run has full access to your connected tools, plugins, and MCP servers.

Scheduled tasks only run while your computer is awake and Claude Desktop is open. If a run is missed, Cowork executes it automatically when the app reopens. For always-on scheduling, use GitHub Actions (Option B) instead. Available on all paid plans (Pro, Max, Team, Enterprise).

Troubleshooting

When 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 don't want to set up a Salesforce Connected App for OAuth
  • You want to run tasks in the background via Claude Cowork while focusing on other work
  • You want to modify SOQL filters in natural language ("only enrich leads from the webinar list")

When to switch to a dedicated tool

  • You need real-time enrichment within seconds of lead creation (use n8n with a Salesforce Trigger)
  • You want enrichment to run automatically with zero human involvement
  • Multiple team members need to manage enrichment settings through a visual interface

Common questions

Does the agent write code every time I run the skill?

Yes. The agent reads the reference files, generates a Python script tailored to your request, runs it, and reports results. This means you can ask for variations ("only enrich leads from this month", "also update the Account's industry") without modifying any files.

How many Apollo credits does a typical run use?

1 credit per lead, regardless of whether Apollo finds a match. A daily run of 50 leads uses 50 credits. On the Basic plan ($49/mo, 900 credits), that's 18 days of daily runs. The Professional plan ($79/mo) includes more credits and higher rate limits.

Can I enrich Contacts instead of Leads?

Yes. Tell the agent: "Enrich Contacts instead of Leads — query the Contact object." The agent will adjust the SOQL query and update endpoints accordingly. The field mapping is the same since Title, Phone, and Department are standard fields on both objects.

What's the difference between this and Apollo's native Salesforce integration?

Apollo's native integration syncs bi-directionally every 30 minutes with automatic deduplication. This Claude Code skill gives you more control — you choose which leads to enrich, which fields to update, and when to run. The native integration is better for always-on sync; the skill is better for targeted backfill.

Cost

  • Apollo: 1 credit per enrichment. Basic plan ($49/mo) = 900 credits. Professional ($79/mo) includes more credits and higher rate limits.
  • Salesforce: API usage included in your Salesforce plan (15,000 API calls per 24-hour period on Enterprise Edition).
  • Claude Code: Usage-based pricing per conversation.
  • GitHub Actions: Free tier covers daily scheduled runs.

Looking to scale your AI operations?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.