AP

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.zip

Installs 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.
45 charsno explicit “when” trigger
Advanced

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

You give it
CSV or JSON contact data
You get back
Migrated contact records and migration report

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

IssueResolution
403 on createContacts API requires master key
Bulk create failsFalls back to individual POST /contacts calls
Duplicate contactsApollo's bulk_create handles dedup — returns existing_contacts
Field mapping errorReview source field names, check for case sensitivity
Rate limitedIncrease 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

Apollo master API keyNode.js 18+Source CRM export in CSV or JSON format

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.

SkillInstallsUpdatedSafetyDifficulty
apollo-migration-deep-dive (this skill)127dCautionAdvanced
apollo-data-handling127dCautionIntermediate
fireflies-core-workflow-a127dCautionIntermediate
azure-ai-document-intelligence-ts03moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

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".

12

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".

12

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.

02

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".

00

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.

212

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".

12

Search skills

Search the agent skills registry