apollo-migration-deep-dive
Strategy and technical guidance for migrating CRM contacts and companies to Apollo.io.
Install
mkdir -p .claude/skills/apollo-migration-deep-dive && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7729" && unzip -o skill.zip -d .claude/skills/apollo-migration-deep-dive && rm skill.zipInstalls to .claude/skills/apollo-migration-deep-dive
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Comprehensive Apollo.io migration strategies.Key capabilities
- →Map source CRM fields to Apollo contact fields
- →Validate data quality before migration
- →Execute bulk contact creation with deduplication
- →Perform rollback of migrated contacts
How it works
The skill maps source data to Apollo fields, validates the records, and uses the bulk create endpoint to import contacts while handling deduplication.
Inputs & outputs
When to use apollo-migration-deep-dive
- →Migrate CRM data to Apollo
- →Perform field mapping for contact imports
- →Execute bulk contact creation
- →Reconcile data across CRM platforms
About this skill
Apollo Migration Deep Dive
Current State
!npm list 2>/dev/null | head -10
Overview
Migrate contact and company data into Apollo.io from other CRMs (Salesforce, HubSpot) or CSV sources. Uses Apollo's Contacts API for creating/updating contacts and Bulk Create Contacts endpoint for high-throughput imports (up to 100 contacts per call). Covers field mapping, assessment, batch processing, reconciliation, and rollback.
Prerequisites
- Apollo master API key (Contacts API requires master key)
- Node.js 18+
- Source CRM export in CSV or JSON format
Instructions
Step 1: Define Field Mappings
// src/migration/field-map.ts
interface FieldMapping {
source: string;
target: string; // Apollo Contacts API field
transform?: (v: any) => any;
required: boolean;
}
// Salesforce -> Apollo
const salesforceMap: FieldMapping[] = [
{ source: 'FirstName', target: 'first_name', required: true },
{ source: 'LastName', target: 'last_name', required: true },
{ source: 'Email', target: 'email', required: true },
{ source: 'Title', target: 'title', required: false },
{ source: 'Phone', target: 'phone_number', required: false },
{ source: 'Company', target: 'organization_name', required: false },
{ source: 'Website', target: 'website_url', required: false,
transform: (url: string) => url?.startsWith('http') ? url : `https://${url}` },
{ source: 'LinkedIn', target: 'linkedin_url', required: false },
];
// HubSpot -> Apollo
const hubspotMap: FieldMapping[] = [
{ source: 'firstname', target: 'first_name', required: true },
{ source: 'lastname', target: 'last_name', required: true },
{ source: 'email', target: 'email', required: true },
{ source: 'jobtitle', target: 'title', required: false },
{ source: 'phone', target: 'phone_number', required: false },
{ source: 'company', target: 'organization_name', required: false },
{ source: 'website', target: 'website_url', required: false },
];
function mapRecord(record: Record<string, any>, mappings: FieldMapping[]): Record<string, any> {
const mapped: Record<string, any> = {};
for (const m of mappings) {
let value = record[m.source];
if (m.required && !value) throw new Error(`Missing: ${m.source}`);
if (value && m.transform) value = m.transform(value);
if (value) mapped[m.target] = value;
}
return mapped;
}
Step 2: Pre-Migration Assessment
// src/migration/assessment.ts
import fs from 'fs';
import { parse } from 'csv-parse/sync';
async function assess(csvPath: string, mappings: FieldMapping[]) {
const records = parse(fs.readFileSync(csvPath, 'utf-8'), { columns: true, skip_empty_lines: true });
const stats = { total: records.length, valid: 0, invalid: 0,
missing: {} as Record<string, number>, duplicateEmails: 0, errors: [] as string[] };
const emails = new Set<string>();
for (const record of records) {
try {
mapRecord(record, mappings);
const email = record.Email ?? record.email;
if (emails.has(email)) stats.duplicateEmails++;
else emails.add(email);
stats.valid++;
} catch (err: any) {
stats.invalid++;
const field = err.message.replace('Missing: ', '');
stats.missing[field] = (stats.missing[field] ?? 0) + 1;
if (stats.errors.length < 5) stats.errors.push(err.message);
}
}
console.log(`Total: ${stats.total}, Valid: ${stats.valid}, Invalid: ${stats.invalid}, Dupes: ${stats.duplicateEmails}`);
if (Object.keys(stats.missing).length) console.log('Missing fields:', stats.missing);
return stats;
}
Step 3: Batch Migration Using Bulk Create
Apollo's Bulk Create Contacts endpoint creates up to 100 contacts per call with intelligent deduplication.
// src/migration/batch-worker.ts
import axios from 'axios';
const client = axios.create({
baseURL: 'https://api.apollo.io/api/v1',
headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
});
interface MigrationResult {
total: number; created: number; existing: number; failed: number;
createdIds: string[];
errors: Array<{ record: any; error: string }>;
}
async function migrateBatch(records: Record<string, any>[], batchSize: number = 100): Promise<MigrationResult> {
const result: MigrationResult = { total: records.length, created: 0, existing: 0, failed: 0,
createdIds: [], errors: [] };
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
try {
// Bulk create endpoint handles deduplication
const { data } = await client.post('/contacts/bulk_create', {
contacts: batch,
});
const newContacts = data.contacts ?? [];
const existingContacts = data.existing_contacts ?? [];
result.created += newContacts.length;
result.existing += existingContacts.length;
result.createdIds.push(...newContacts.map((c: any) => c.id));
} catch (err: any) {
// Fall back to individual creates
for (const record of batch) {
try {
const { data } = await client.post('/contacts', record);
result.created++;
result.createdIds.push(data.contact.id);
} catch (e: any) {
result.failed++;
result.errors.push({ record, error: e.response?.data?.message ?? e.message });
}
}
}
// Rate limit: 100 requests/min for contacts
if (i + batchSize < records.length) {
await new Promise((r) => setTimeout(r, 1000));
}
console.log(`Progress: ${Math.min(i + batchSize, records.length)}/${records.length}`);
}
return result;
}
Step 4: Post-Migration Reconciliation
async function reconcile(sourceRecords: Record<string, any>[]) {
let matched = 0, missing = 0, mismatched = 0;
for (const source of sourceRecords.slice(0, 100)) { // Sample reconciliation
const { data } = await client.post('/contacts/search', {
q_keywords: source.email, per_page: 1,
});
const contact = data.contacts?.[0];
if (!contact) { missing++; continue; }
const nameMatch = contact.first_name === source.first_name && contact.last_name === source.last_name;
if (nameMatch) matched++;
else { mismatched++; console.warn(`Mismatch: ${source.email}`); }
}
console.log(`Reconciliation: ${matched} matched, ${missing} missing, ${mismatched} mismatched`);
return { matched, missing, mismatched };
}
Step 5: Rollback
async function rollback(contactIds: string[]) {
console.log(`Rolling back ${contactIds.length} contacts...`);
let deleted = 0;
for (let i = 0; i < contactIds.length; i += 50) {
const batch = contactIds.slice(i, i + 50);
for (const id of batch) {
try { await client.delete(`/contacts/${id}`); deleted++; }
catch (err: any) { console.error(`Failed: ${id}: ${err.message}`); }
}
await new Promise((r) => setTimeout(r, 500));
console.log(`Rollback: ${Math.min(i + 50, contactIds.length)}/${contactIds.length}`);
}
console.log(`Rolled back ${deleted}/${contactIds.length} contacts`);
}
Output
- Field mappings for Salesforce and HubSpot to Apollo Contacts API
- Pre-migration assessment with validation, duplicates, and missing fields
- Batch migration via
POST /contacts/bulk_create(100 per call) - Post-migration reconciliation sampling
- Rollback procedure deleting created contacts
Error Handling
| Issue | Resolution |
|---|---|
| 403 on create | Contacts API requires master key |
| Bulk create fails | Falls back to individual POST /contacts calls |
| Duplicate contacts | Apollo's bulk_create handles dedup — returns existing_contacts |
| Field mapping error | Review source field names, check for case sensitivity |
| Rate limited | Increase delay between batches |
Examples
Full Migration Pipeline
const assessment = await assess('./salesforce-export.csv', salesforceMap);
if (assessment.invalid > assessment.total * 0.1) {
console.error('Too many invalid records (>10%). Clean data first.');
process.exit(1);
}
const records = parseCsv('./salesforce-export.csv').map((r) => mapRecord(r, salesforceMap));
const result = await migrateBatch(records, 100);
console.log(`Created: ${result.created}, Existing: ${result.existing}, Failed: ${result.failed}`);
// Save contact IDs for potential rollback
fs.writeFileSync('migration-ids.json', JSON.stringify(result.createdIds));
await reconcile(records);
Resources
Next Steps
After migration, verify data with apollo-prod-checklist.
When not to use it
- →Migrating data without a master API key
- →Importing datasets exceeding API rate limits without batching
Prerequisites
Limitations
- →Requires master API key for Contacts API
- →Bulk create limited to 100 contacts per call
How it compares
It provides a programmatic, batch-based migration path instead of manual CSV uploads via the web dashboard.
Compared to similar skills
apollo-migration-deep-dive side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| apollo-migration-deep-dive (this skill) | 1 | 27d | Caution | Advanced |
| apollo-data-handling | 1 | 27d | Caution | Intermediate |
| fireflies-core-workflow-a | 1 | 27d | Caution | Intermediate |
| azure-ai-document-intelligence-ts | 0 | 3mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
apollo-data-handling
jeremylongshore
Apollo.io data management and compliance. Use when handling contact data, implementing GDPR compliance, or managing data exports and retention. Trigger with phrases like "apollo data", "apollo gdpr", "apollo compliance", "apollo data export", "apollo data retention", "apollo pii".
fireflies-core-workflow-a
jeremylongshore
Execute Fireflies.ai primary workflow: Core Workflow A. Use when implementing primary use case, building main features, or core integration tasks. Trigger with phrases like "fireflies main workflow", "primary task with fireflies".
azure-ai-document-intelligence-ts
microsoft
Extract text, tables, and structured data from documents using Azure Document Intelligence (@azure-rest/ai-document-intelligence). Use when processing invoices, receipts, IDs, forms, or building custom document models.
openevidence-core-workflow-b
jeremylongshore
Execute OpenEvidence DeepConsult workflow for comprehensive medical research. Use when implementing deep research synthesis, complex clinical questions, or when physicians need extensive literature review. Trigger with phrases like "openevidence deepconsult", "deep research", "comprehensive evidence", "literature synthesis".
segment-cdp
davila7
Expert patterns for Segment Customer Data Platform including Analytics.js, server-side tracking, tracking plans with Protocols, identity resolution, destinations configuration, and data governance best practices. Use when: segment, analytics.js, customer data platform, cdp, tracking plan.
groq-cost-tuning
jeremylongshore
Optimize Groq costs through tier selection, sampling, and usage monitoring. Use when analyzing Groq billing, reducing API costs, or implementing usage monitoring and budget alerts. Trigger with phrases like "groq cost", "groq billing", "reduce groq costs", "groq pricing", "groq expensive", "groq budget".