Complete Apollo integration skill pack with 24 skills covering sales engagement, prospecting, sequencing, analytics, and outbound automation. Flagship tier vendor pack.
Installation
Open Claude Code and run this command:
/plugin install apollo-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
Skills (24)
Configure Apollo.
Apollo CI Integration
Overview
Set up CI/CD pipelines for Apollo.io integrations with GitHub Actions. Uses MSW mocks for unit tests (zero API calls), sandbox tokens for staging, and live API tests gated to main branch only. Apollo's sandbox token returns dummy data without consuming credits.
Prerequisites
- GitHub repository with Actions enabled
- Apollo master API key + sandbox token
- Node.js 18+
Instructions
Step 1: Store Secrets in GitHub
# Master API key for integration tests (main branch only)
gh secret set APOLLO_API_KEY --body "$APOLLO_API_KEY"
# Sandbox token for staging tests (safe, no credits)
gh secret set APOLLO_SANDBOX_KEY --body "$APOLLO_SANDBOX_KEY"
Step 2: GitHub Actions Workflow
# .github/workflows/apollo-ci.yml
name: Apollo CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run lint
- run: npm run typecheck
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm test
# Unit tests use MSW mocks — zero API calls
integration-tests:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [unit-tests]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- name: Apollo Health Check
env:
APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "x-api-key: $APOLLO_API_KEY" \
"https://api.apollo.io/api/v1/auth/health")
echo "Apollo API: HTTP $STATUS"
[ "$STATUS" = "200" ] || exit 1
- run: npm run test:integration
env:
APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for hardcoded API keys
run: |
if grep -rn 'x-api-key.*[a-zA-Z0-9]\{20,\}' src/ --include='*.ts' --include='*.js'; then
echo "Potential hardcoded API key found!"
exit 1
fi
echo "No hardcoded secrets"
Step 3: MSW-Based Unit Tests
// src/__tests__/apollo.test.ts
import { describe, it, eDiagnose and fix common Apollo.
Apollo Common Errors
Overview
Comprehensive guide to diagnosing and fixing Apollo.io API errors. Apollo uses x-api-key header authentication and the base URL https://api.apollo.io/api/v1/. Apollo distinguishes between master and standard API keys — many endpoints require master keys.
Prerequisites
- Valid Apollo.io API credentials
- Node.js 18+ or Python 3.10+
Instructions
Step 1: Identify the Error Category
// src/apollo/error-handler.ts
import { AxiosError } from 'axios';
type ErrorCategory = 'auth' | 'permission' | 'rate_limit' | 'validation' | 'server' | 'network';
function categorizeError(err: AxiosError): ErrorCategory {
if (!err.response) return 'network';
switch (err.response.status) {
case 401: return 'auth';
case 403: return 'permission';
case 429: return 'rate_limit';
case 400: case 422: return 'validation';
default: return err.response.status >= 500 ? 'server' : 'validation';
}
}
Step 2: Handle 401 — Invalid API Key
// Most common cause: missing x-api-key header or wrong key format
async function diagnoseAuth() {
try {
const response = await fetch('https://api.apollo.io/api/v1/auth/health', {
headers: { 'x-api-key': process.env.APOLLO_API_KEY! },
});
const data = await response.json();
if (data.is_logged_in) {
console.log('API key is valid');
} else {
console.error('API key is invalid or expired');
console.error(' Generate a new one at: Apollo > Settings > Integrations > API Keys');
}
} catch (err: any) {
console.error('Cannot reach Apollo API:', err.message);
}
}
Common 401 causes:
- Using
api_keyquery parameter instead ofx-api-keyheader - Key was revoked or regenerated in the dashboard
- Key has trailing whitespace (check with
echo -n "$APOLLOAPIKEY" | wc -c)
Step 3: Handle 403 — Wrong Key Type
Standard API key: search + enrichment only
Master API key: full access (contacts, sequences, deals, tasks)
Endpoints that require a master key:
POST /contacts(create/update)POST /emailer_campaigns/search(sequences)POST /emailercampaigns/{id}/addcontact_idsPOST /opportunities(deals)POST /tasks(tasks)DELETE /contacts/{id}
// Diagnose: test a master-key-only endpoint
async function diagnoseMasteImplement Apollo.
Apollo Core Workflow A: Lead Search & Enrichment
Overview
Build the core Apollo.io prospecting pipeline: search for people and organizations, then enrich the best leads. Key distinction — search is free (no credits), enrichment costs credits. This skill uses the correct endpoints and x-api-key header authentication.
Prerequisites
- Completed
apollo-install-authsetup - Valid Apollo API key with search + enrichment permissions
- Understanding of your Ideal Customer Profile (ICP)
Instructions
Step 1: Search for People (Free — No Credits)
The People API Search endpoint (POST /mixedpeople/apisearch) searches Apollo's 275M+ database. It does not return emails or phone numbers — use enrichment for that.
// src/workflows/lead-search.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 PersonSearchParams {
domains: string[];
titles?: string[];
seniorities?: string[]; // "c_suite", "vp", "director", "manager", "senior"
locations?: string[]; // "United States", "San Francisco, California"
page?: number;
perPage?: number;
}
export async function searchPeople(params: PersonSearchParams) {
const { data } = await client.post('/mixed_people/api_search', {
q_organization_domains_list: params.domains,
person_titles: params.titles,
person_seniorities: params.seniorities,
person_locations: params.locations,
include_similar_titles: true,
page: params.page ?? 1,
per_page: params.perPage ?? 25,
});
return {
people: data.people,
total: data.pagination.total_entries,
totalPages: data.pagination.total_pages,
page: data.pagination.page,
};
}
Step 2: Search for Organizations (Free)
// src/workflows/org-search.ts
interface OrgSearchParams {
keywords?: string;
locations?: string[];
employeeRanges?: string[]; // ["1,10", "11,50", "51,200", "201,500"]
industries?: string[];
revenueRanges?: string[]; // ["0,1000000", "1000000,10000000"]
}
export async function searchOrganizations(params: OrgSearchParams) {
const { data } = await client.post('/mixed_companies/search', {
q_organization_keyword_tags: params.keywords ? [params.keywords] : undefined,
organization_locations: params.locations,
organization_num_employees_ranges: params.employeeRanges,
organization_industry_tag_ids: params.industries,
organization_revenue_ranges: params.revenueRanges,
page: Implement Apollo.
Apollo Core Workflow B: Email Sequences & Outreach
Overview
Build Apollo.io email sequencing and outreach automation via the REST API. Sequences in Apollo are called "emailer_campaigns" in the API. This covers listing, searching, adding contacts, tracking engagement, and managing sequence lifecycle. All endpoints require a master API key.
Prerequisites
- Completed
apollo-core-workflow-a(lead search) - Apollo account with Sequences feature enabled
- Connected email account in Apollo (Settings > Channels > Email)
- Master API key (not standard)
Instructions
Step 1: Search for Existing Sequences
// src/workflows/sequences.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! },
});
export async function searchSequences(query?: string) {
const { data } = await client.post('/emailer_campaigns/search', {
q_name: query, // optional name filter
page: 1,
per_page: 25,
});
return data.emailer_campaigns.map((seq: any) => ({
id: seq.id,
name: seq.name,
active: seq.active,
numSteps: seq.num_steps ?? seq.emailer_steps?.length ?? 0,
stats: {
totalContacts: seq.unique_scheduled ?? 0,
delivered: seq.unique_delivered ?? 0,
opened: seq.unique_opened ?? 0,
replied: seq.unique_replied ?? 0,
bounced: seq.unique_bounced ?? 0,
},
createdAt: seq.created_at,
}));
}
Step 2: Get Email Accounts for Sending
Before adding contacts to a sequence, you need the email account ID that will send the messages.
export async function getEmailAccounts() {
const { data } = await client.get('/email_accounts');
return data.email_accounts.map((acct: any) => ({
id: acct.id,
email: acct.email,
sendingEnabled: acct.active,
provider: acct.type, // "gmail", "outlook", "smtp"
dailySendLimit: acct.daily_email_limit,
}));
}
Step 3: Add Contacts to a Sequence
The addcontactids endpoint enrolls contacts into an existing sequence. You must specify which email account sends the messages.
export async function addContactsToSequence(
sequenceId: string,
contactIds: string[],
emailAccountId: string,
) {
const { data } = await client.post(
`/emailer_campaigns/${sequenceId}/add_contact_ids`,
{
contact_ids: contactIds,
emailer_campaign_id: sequenceId,
send_email_from_email_account_id: emailAccountId,
sequence_active_in_other_campaigns: false, // skip if already in another sequence
},
);
return {
added: data.contaOptimize Apollo.
Apollo Cost Tuning
Overview
Optimize Apollo.io API costs through credit-aware enrichment. Key cost model: search is free, enrichment costs credits. Apollo charges per unique contact/company lookup. Credits do not roll over. Strategies: deduplicate before enriching, score leads before spending credits, and track daily budget.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Understand Apollo's Credit Model
Action | Credits | Notes
----------------------------+---------+-----------------------------------
People Search | 0 | /mixed_people/api_search (free!)
Organization Search | 0 | /mixed_companies/search (free!)
People Enrichment (single) | 1 | /people/match
People Enrichment (bulk) | 1/match | /people/bulk_match (up to 10/call)
Organization Enrichment | 1 | /organizations/enrich
Reveal Personal Email | +1 | reveal_personal_emails param
Reveal Phone Number | +1 | reveal_phone_number param
Plans (approximate):
- Free: 50 credits/month
- Basic: 1,200 credits/month (~$0.04/credit)
- Professional: 6,000 credits/month
- Organization: 12,000+ credits/month
Step 2: Track Credit Usage
// src/cost/credit-tracker.ts
class CreditTracker {
private daily: Map<string, number> = new Map();
private readonly budget: number;
constructor(dailyBudget: number = 200) {
this.budget = dailyBudget;
}
record(count: number = 1) {
const today = new Date().toISOString().split('T')[0];
this.daily.set(today, (this.daily.get(today) ?? 0) + count);
}
todayUsage(): number {
const today = new Date().toISOString().split('T')[0];
return this.daily.get(today) ?? 0;
}
isOverBudget(): boolean {
return this.todayUsage() >= this.budget;
}
report(): string {
const used = this.todayUsage();
return `${used}/${this.budget} credits (${Math.round((used / this.budget) * 100)}%)`;
}
}
export const creditTracker = new CreditTracker(
parseInt(process.env.APOLLO_DAILY_CREDIT_BUDGET ?? '200', 10),
);
Step 3: Deduplicate Before Enriching
// src/cost/dedup.ts
import { LRUCache } from 'lru-cache';
// Track enriched contacts to avoid paying twice
const enrichedCache = new LRUCache<string, boolean>({
max: 50_000,
ttl: 30 * 24 * 60 * 60 * 1000, // 30 days
});
export function enrichmentKey(params: { email?: string; linkedin_url?: string;
first_name?: string; last_name?: string; organization_domain?: string }): string {
// Prefer email as unique key, fall back to LinkedIn, then name+domain
return params.email
?? params.linkedin_url
Apollo.
Apollo Data Handling
Overview
Data management, compliance, and governance for Apollo.io contact data. Apollo's database contains 275M+ contacts with PII (emails, phones, LinkedIn profiles). This covers GDPR subject access/erasure, data retention, field-level encryption, and audit logging — using the real Apollo Contacts API endpoints.
Prerequisites
- Apollo master API key (contacts/delete requires master key)
- Node.js 18+
Instructions
Step 1: GDPR Subject Access Request (SAR)
Find all data Apollo has on a person and export it.
// src/data/gdpr.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 SubjectAccessReport {
email: string;
dataFound: boolean;
crmContact?: Record<string, any>;
apolloDatabaseMatch?: Record<string, any>;
activeSequences: string[];
exportedAt: string;
}
export async function handleSAR(email: string): Promise<SubjectAccessReport> {
const report: SubjectAccessReport = {
email, dataFound: false, activeSequences: [],
exportedAt: new Date().toISOString(),
};
// 1. Search your CRM contacts (contacts you've saved)
const { data: crmData } = await client.post('/contacts/search', {
q_keywords: email,
per_page: 1,
});
if (crmData.contacts?.length > 0) {
const c = crmData.contacts[0];
report.dataFound = true;
report.crmContact = {
id: c.id, name: c.name, email: c.email, title: c.title,
phone: c.phone_numbers, organization: c.organization_name,
city: c.city, state: c.state, country: c.country,
createdAt: c.created_at, updatedAt: c.updated_at,
contactStage: c.contact_stage_id,
labels: c.label_ids,
};
report.activeSequences = c.emailer_campaign_ids ?? [];
}
// 2. Check Apollo's database (enrichment data)
try {
const { data: enrichData } = await client.post('/people/match', { email });
if (enrichData.person) {
report.dataFound = true;
report.apolloDatabaseMatch = {
name: enrichData.person.name,
title: enrichData.person.title,
seniority: enrichData.person.seniority,
city: enrichData.person.city,
linkedinUrl: enrichData.person.linkedin_url,
organization: enrichData.person.organization?.name,
};
}
} catch { /* person not found in Apollo DB */ }
return report;
}
Step 2: Right to Erasure (Delete)
export async function handleErasure(email: string): Promise<{
email: string; erased: boolean; sequencesRemoved: number;
}> {
// 1. Find the CRM contact
const { data } = await client.post('/contacts/search', {
q_keywords: email, peCollect Apollo.
Apollo Debug Bundle
Current State
!node --version 2>/dev/null || echo 'N/A'
!python3 --version 2>/dev/null || echo 'N/A'
!uname -a
Overview
Collect comprehensive debug information for Apollo.io API issues. Generates a JSON diagnostic bundle with environment info, connectivity tests, rate limit status, key type verification, and sanitized request/response logs.
Prerequisites
APOLLOAPIKEYenvironment variable set- Node.js 18+ or Python 3.10+
Instructions
Step 1: Create the Debug Bundle Collector
// src/scripts/debug-bundle.ts
import axios from 'axios';
import os from 'os';
const BASE_URL = 'https://api.apollo.io/api/v1';
interface DebugBundle {
timestamp: string;
environment: Record<string, string>;
connectivity: { reachable: boolean; latencyMs: number; statusCode?: number };
keyType: 'master' | 'standard' | 'invalid' | 'unknown';
rateLimits: { limit?: string; remaining?: string; window?: string };
endpointTests: Array<{ endpoint: string; status: number | string; ok: boolean }>;
systemInfo: Record<string, string>;
}
async function collectDebugBundle(): Promise<DebugBundle> {
const headers = {
'Content-Type': 'application/json',
'x-api-key': process.env.APOLLO_API_KEY ?? '',
};
const bundle: DebugBundle = {
timestamp: new Date().toISOString(),
environment: {
nodeVersion: process.version,
platform: os.platform(),
arch: os.arch(),
apiKeySet: process.env.APOLLO_API_KEY ? 'yes (redacted)' : 'NOT SET',
apiKeyLength: String(process.env.APOLLO_API_KEY?.length ?? 0),
apiKeyPrefix: process.env.APOLLO_API_KEY?.slice(0, 4) + '...' ?? 'N/A',
},
connectivity: { reachable: false, latencyMs: 0 },
keyType: 'unknown',
rateLimits: {},
endpointTests: [],
systemInfo: {},
};
return bundle;
}
Step 2: Test Connectivity and Key Type
async function testConnectivity(bundle: DebugBundle) {
const headers = { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! };
// Test basic auth
const start = Date.now();
try {
const resp = await axios.get(`${BASE_URL}/auth/health`, { headers, timeout: 10_000 });
bundle.connectivity = { reachable: true, latencyMs: Date.now() - start, statusCode: resp.status };
bundle.keyType = resp.data.is_logged_in ? 'standard' : 'invalid'; // at minimum standard
} catch (err: any) {
bundle.connectivity = { reachable: false, latencyMs: Date.now() - start, statusCode: err.response?.status };
return;
}
// Test master key access
try {
await axiDeploy Apollo.
Apollo Deploy Integration
Overview
Deploy Apollo.io integrations to production with configurations for Vercel, GCP Cloud Run, and Kubernetes. All configurations use x-api-key header auth, health check endpoints verifying Apollo connectivity, and secret management best practices.
Prerequisites
- Valid Apollo master API key
- Node.js 18+
- Target platform CLI installed (vercel, gcloud, or kubectl)
Instructions
Step 1: Health Check Endpoint
Every deployment needs a health endpoint that verifies Apollo API connectivity.
// src/health.ts
import axios from 'axios';
import { Router } from 'express';
export const healthRouter = Router();
healthRouter.get('/health', async (req, res) => {
const checks: Record<string, string> = {
apiKey: process.env.APOLLO_API_KEY ? 'set' : 'MISSING',
nodeEnv: process.env.NODE_ENV ?? 'not set',
};
try {
const start = Date.now();
const resp = await axios.get('https://api.apollo.io/api/v1/auth/health', {
headers: { 'x-api-key': process.env.APOLLO_API_KEY! },
timeout: 5000,
});
checks.apollo = resp.data.is_logged_in ? `ok (${Date.now() - start}ms)` : 'invalid key';
} catch (err: any) {
checks.apollo = `error: ${err.response?.status ?? err.message}`;
}
const healthy = checks.apollo.startsWith('ok') && checks.apiKey === 'set';
res.status(healthy ? 200 : 503).json({ status: healthy ? 'healthy' : 'unhealthy', checks });
});
Step 2: Deploy to GCP Cloud Run
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 8080
CMD ["node", "dist/index.js"]
# Store API key in GCP Secret Manager
echo -n "$APOLLO_API_KEY" | gcloud secrets create apollo-api-key --data-file=-
# Deploy with secret injection
gcloud run deploy apollo-integration \
--source . \
--region us-central1 \
--set-secrets "APOLLO_API_KEY=apollo-api-key:latest" \
--set-env-vars "NODE_ENV=production" \
--min-instances 1 --max-instances 10 \
--port 8080
Step 3: Deploy to Vercel
{
"name": "apollo-integration",
"builds": [{ "src": "src/index.ts", "use": "@vercel/node" }],
"routes": [{ "src": "/(.*)", "dest": "src/index.ts" }],
"env": { "APOLLO_API_KEY": "@apollo-api-key", "NODE_ENV": &qEnterprise role-based access control for Apollo.
Apollo Enterprise RBAC
Overview
Role-based access control for Apollo.io API integrations. Apollo API keys are all-or-nothing (standard vs master), so RBAC must be implemented in your application layer as a proxy between users and the Apollo API. This skill builds a permission matrix, scoped API key system, Express middleware, and admin audit endpoints.
Prerequisites
- Apollo master API key
- Node.js 18+ with Express
Instructions
Step 1: Define Roles and Permission Matrix
Map Apollo API operations to team roles. Apollo's API has two main categories:
- Read-only: search (free), enrichment (credits)
- Write: contacts CRUD, sequences, deals, tasks
// src/rbac/roles.ts
export type Role = 'viewer' | 'analyst' | 'sales_rep' | 'sales_manager' | 'admin';
export interface Permission {
searchPeople: boolean; // /mixed_people/api_search (free)
searchOrganizations: boolean; // /mixed_companies/search (free)
enrichPerson: boolean; // /people/match (1 credit)
bulkEnrich: boolean; // /people/bulk_match (credits)
enrichOrg: boolean; // /organizations/enrich (1 credit)
manageContacts: boolean; // /contacts CRUD (master key)
manageSequences: boolean; // /emailer_campaigns/* (master key)
manageDeals: boolean; // /opportunities/* (master key)
exportPII: boolean; // download contacts with email/phone
viewAnalytics: boolean; // sequence stats, usage
manageTeam: boolean; // create/revoke scoped keys
}
export const PERMISSIONS: Record<Role, Permission> = {
viewer: {
searchPeople: true, searchOrganizations: true, enrichPerson: false,
bulkEnrich: false, enrichOrg: false, manageContacts: false,
manageSequences: false, manageDeals: false, exportPII: false,
viewAnalytics: true, manageTeam: false,
},
analyst: {
searchPeople: true, searchOrganizations: true, enrichPerson: true,
bulkEnrich: false, enrichOrg: true, manageContacts: false,
manageSequences: false, manageDeals: false, exportPII: false,
viewAnalytics: true, manageTeam: false,
},
sales_rep: {
searchPeople: true, searchOrganizations: true, enrichPerson: true,
bulkEnrich: false, enrichOrg: true, manageContacts: true,
manageSequences: true, manageDeals: true, exportPII: false,
viewAnalytics: false, manageTeam: false,
},
sales_manager: {
searchPeople: true, searchOrganizations: true, enrichPerson: true,
bulkEnrich: true, enrichOrg: true, manageContacts: true,
manageSequences: true, manageDeals: true, exportPII: true,
viewAnalytics: true, manageTeam: true,
},
admin: {
searchPeople: true, searchOrganizations: true, enrichPerson: true,
bulkEnrich: true, enrichOrg: true, manageContacts: true,
manageSequences: true, manageDeals:Create a minimal working Apollo.
Apollo Hello World
Overview
Minimal working example demonstrating the three core Apollo.io API operations: people search, person enrichment, and organization enrichment. Uses the correct x-api-key header and api.apollo.io/api/v1/ base URL.
Prerequisites
- Completed
apollo-install-authsetup - Valid API key configured in
APOLLOAPIKEYenvironment variable
Instructions
Step 1: Search for People (No Credits Consumed)
The People API Search endpoint finds contacts in Apollo's 275M+ database. This endpoint is free — it does not consume enrichment credits, but it also does not return emails or phone numbers.
// hello-apollo.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!,
},
});
// People Search — POST /mixed_people/api_search
async function searchPeople() {
const { data } = await client.post('/mixed_people/api_search', {
q_organization_domains_list: ['apollo.io'],
person_titles: ['engineer'],
person_seniorities: ['senior', 'manager'],
page: 1,
per_page: 10,
});
console.log(`Found ${data.pagination.total_entries} contacts`);
data.people.forEach((person: any) => {
console.log(` ${person.name} — ${person.title} at ${person.organization?.name}`);
});
return data;
}
searchPeople().catch(console.error);
Step 2: Enrich a Single Person (Consumes 1 Credit)
The People Enrichment endpoint returns full contact details including email and phone.
// Enrich by email, LinkedIn URL, or name+domain combo
async function enrichPerson() {
const { data } = await client.post('/people/match', {
email: 'tim@apollo.io',
// Alternative identifiers:
// linkedin_url: 'https://www.linkedin.com/in/...',
// first_name: 'Tim', last_name: 'Zheng', organization_domain: 'apollo.io',
reveal_personal_emails: false,
reveal_phone_number: false,
});
if (!data.person) {
console.log('No match found');
return;
}
const p = data.person;
console.log(`Name: ${p.name}`);
console.log(`Title: ${p.title}`);
console.log(`Email: ${p.email}`);
console.log(`Company: ${p.organization?.name}`);
console.log(`LinkedIn: ${p.linkedin_url}`);
}
Step 3: Enrich an Organization (Consumes 1 Credit)
// Organization Enrichment — GET /organizations/enrich
async function enrichOrg() {
const { data } = await client.get('/organizations/enrich', {
params: { domain: 'apollo.io' },
});
const orgApollo.
Apollo Incident Runbook
Overview
Structured incident response for Apollo.io API failures. Covers severity classification, quick diagnosis, circuit breaker implementation, graceful degradation, and post-incident review. Apollo's public status page is at status.apollo.io.
Prerequisites
- Valid Apollo API key
- Access to monitoring dashboards
Instructions
Step 1: Classify Severity
Severity | Criteria | Response Time
---------+---------------------------------------------+--------------
P1 | Apollo API completely unreachable | 15 min
| All enrichments/searches returning 5xx |
P2 | Partial failures (>10% error rate) | 1 hour
| Rate limiting blocking critical workflows |
P3 | Intermittent errors (<10%), degraded latency | 4 hours
| Non-critical endpoint failures |
P4 | Cosmetic issues, minor data inconsistencies | Next sprint
Step 2: Quick Diagnosis Script
#!/bin/bash
# scripts/apollo-diagnosis.sh
set -euo pipefail
echo "=== Apollo Quick Diagnosis $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
# 1. Check Apollo status page
echo -e "\n--- Status Page ---"
curl -s https://status.apollo.io/api/v2/status.json 2>/dev/null | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Status: {d[\"status\"][\"description\"]}')" \
2>/dev/null || echo "Could not reach status page"
# 2. Test auth
echo -e "\n--- Auth Check ---"
curl -s -w "HTTP %{http_code} in %{time_total}s\n" \
-H "x-api-key: $APOLLO_API_KEY" \
"https://api.apollo.io/api/v1/auth/health" | head -1
# 3. Test people search (free endpoint)
echo -e "\n--- People Search ---"
curl -s -w "HTTP %{http_code} in %{time_total}s\n" -o /dev/null \
-X POST -H "Content-Type: application/json" -H "x-api-key: $APOLLO_API_KEY" \
-d '{"q_organization_domains_list":["apollo.io"],"per_page":1}' \
"https://api.apollo.io/api/v1/mixed_people/api_search"
# 4. Check rate limit headers
echo -e "\n--- Rate Limits ---"
curl -s -D - -o /dev/null \
-X POST -H "Content-Type: application/json" -H "x-api-key: $APOLLO_API_KEY" \
-d '{"q_organization_domains_list":["apollo.io"],"per_page":1}' \
"https://api.apollo.io/api/v1/mixed_people/api_search" 2>/dev/null | grep -i "x-rate-limit" || echo "No rate limit headers"
# 5. DNS resolution
echo -e "\n--- DNS ---"
dig +short api.apollo.io 2>/dev/null || nslookup api.apollo.io 2>/dev/null || echo "DNS lookup failed"
St
Install and configure Apollo.
Apollo Install & Auth
Overview
Set up Apollo.io API client and configure authentication credentials. Apollo uses the x-api-key HTTP header for authentication against the base URL https://api.apollo.io/api/v1/. There is no official SDK — all integrations use the REST API directly.
Prerequisites
- Node.js 18+ or Python 3.10+
- Package manager (npm, pnpm, or pip)
- Apollo.io account with API access (Basic plan or above)
- API key from Apollo dashboard (Settings > Integrations > API Keys)
Instructions
Step 1: Install HTTP Client
set -euo pipefail
# Node.js
npm install axios dotenv
# Python
pip install requests python-dotenv
Step 2: Configure API Key
Apollo supports two API key types:
- Master API key — full access to all endpoints (required for contacts, sequences, deals)
- Standard API key — limited to search and enrichment only
# Create .env file (never commit this)
echo 'APOLLO_API_KEY=your-api-key-here' >> .env
echo '.env' >> .gitignore
Step 3: Create Apollo Client (TypeScript)
// src/apollo/client.ts
import axios, { AxiosInstance } from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const BASE_URL = 'https://api.apollo.io/api/v1';
export function createApolloClient(apiKey?: string): AxiosInstance {
const key = apiKey ?? process.env.APOLLO_API_KEY;
if (!key) throw new Error('APOLLO_API_KEY is not set');
return axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'x-api-key': key,
},
timeout: 30_000,
});
}
export const apolloClient = createApolloClient();
Step 4: Verify Connection
// src/scripts/verify-auth.ts
import { apolloClient } from '../apollo/client';
async function verifyConnection() {
try {
// Use the health endpoint to test connectivity
const response = await apolloClient.get('/auth/health');
console.log('Apollo connection:', response.data.is_logged_in ? 'OK' : 'Invalid key');
} catch (error: any) {
if (error.response?.status === 401) {
console.error('Invalid API key. Generate a new one at:');
console.error(' Apollo Dashboard > Settings > Integrations > API Keys');
} else {
console.error('Connection failed:', error.message);
}
}
}
verifyConnection();
Step 5: Create Apollo Client (Python)
# apollo_client.py
import os
import requests
from dotenv import load_dotenv
load_dotenv()
claConfigure Apollo.
Apollo Local Dev Loop
Overview
Set up an efficient local development workflow for Apollo.io integrations. Includes sandbox API key support, MSW mock server for offline testing, request logging, and npm scripts for daily development. Apollo provides a sandbox API token that returns dummy data without consuming credits.
Prerequisites
- Completed
apollo-install-authsetup - Node.js 18+
- Git repository initialized
Instructions
Step 1: Set Up Environment Files
Apollo offers a sandbox token for testing. Generate one at Settings > Integrations > API Keys > Sandbox.
# .env.example (commit this — team reference)
APOLLO_API_KEY=your-api-key-here
APOLLO_SANDBOX_KEY=your-sandbox-key-here
APOLLO_USE_SANDBOX=false
# .env (gitignored — copy from example)
cp .env.example .env
echo '.env' >> .gitignore
echo '.env.local' >> .gitignore
Step 2: Create a Dev Client with Request Logging
// src/apollo/dev-client.ts
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const apiKey = process.env.APOLLO_USE_SANDBOX === 'true'
? process.env.APOLLO_SANDBOX_KEY
: process.env.APOLLO_API_KEY;
export const devClient = axios.create({
baseURL: 'https://api.apollo.io/api/v1',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey!,
},
timeout: 30_000,
});
// Log all requests in development
devClient.interceptors.request.use((config) => {
console.log(`[Apollo] ${config.method?.toUpperCase()} ${config.url}`);
if (config.data) console.log('[Apollo] Body:', JSON.stringify(config.data, null, 2));
return config;
});
devClient.interceptors.response.use(
(res) => {
const remaining = res.headers['x-rate-limit-remaining'];
console.log(`[Apollo] ${res.status} ${res.config.url}${remaining ? ` — ${remaining} req remaining` : ''}`);
return res;
},
(err) => {
console.error(`[Apollo] ERROR ${err.response?.status}: ${err.response?.data?.message ?? err.message}`);
return Promise.reject(err);
},
);
Step 3: Set Up MSW Mock Server for Offline Testing
// src/mocks/apollo-handlers.ts
import { http, HttpResponse } from 'msw';
const BASE = 'https://api.apollo.io/api/v1';
export const apolloHandlers = [
// People Search (free endpoint)
http.post(`${BASE}/mixed_people/api_search`, () =>
HttpResponse.json({
people: [
{ id: 'mock-1', name: 'Jane Doe', title: 'VP Sales', organization: { name: 'Acme Corp' } },
{ id: 'mock-2', name: 'John Smith', title: 'Director Engineering', organization: { name: 'Acme Corp' } },
],
paComprehensive Apollo.
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/syncConfigure Apollo.
Apollo Multi-Environment Setup
Overview
Configure Apollo.io for development, staging, and production with isolated API keys, environment-specific rate limits, feature gating, and Kubernetes-native secret management. Apollo provides sandbox tokens for testing that return dummy data without consuming credits.
Prerequisites
- Separate Apollo API keys per environment (or sandbox tokens for dev)
- Node.js 18+
Instructions
Step 1: Environment Configuration Schema
// src/config/apollo-config.ts
import { z } from 'zod';
const EnvironmentSchema = z.enum(['development', 'staging', 'production']);
const ApolloEnvConfig = z.object({
environment: EnvironmentSchema,
apiKey: z.string().min(10),
baseUrl: z.string().url().default('https://api.apollo.io/api/v1'),
isSandbox: z.boolean().default(false),
rateLimit: z.object({
maxPerMinute: z.number().min(1),
concurrency: z.number().min(1).max(20),
}),
features: z.object({
enrichment: z.boolean(),
sequences: z.boolean(),
deals: z.boolean(),
bulkEnrichment: z.boolean(),
}),
credits: z.object({
dailyBudget: z.number(),
alertThreshold: z.number(), // percentage
}),
logging: z.object({
level: z.enum(['debug', 'info', 'warn', 'error']),
redactPII: z.boolean(),
}),
});
type ApolloEnvConfig = z.infer<typeof ApolloEnvConfig>;
Step 2: Per-Environment Configs
const configs: Record<string, ApolloEnvConfig> = {
development: {
environment: 'development',
apiKey: process.env.APOLLO_SANDBOX_KEY ?? process.env.APOLLO_API_KEY_DEV!,
baseUrl: 'https://api.apollo.io/api/v1',
isSandbox: true,
rateLimit: { maxPerMinute: 20, concurrency: 2 },
features: { enrichment: true, sequences: false, deals: false, bulkEnrichment: false },
credits: { dailyBudget: 10, alertThreshold: 80 },
logging: { level: 'debug', redactPII: false },
},
staging: {
environment: 'staging',
apiKey: process.env.APOLLO_API_KEY_STAGING!,
baseUrl: 'https://api.apollo.io/api/v1',
isSandbox: false,
rateLimit: { maxPerMinute: 50, concurrency: 5 },
features: { enrichment: true, sequences: true, deals: true, bulkEnrichment: true },
credits: { dailyBudget: 50, alertThreshold: 70 },
logging: { level: 'info', redactPII: true },
},
production: {
environment: 'production',
apiKey: process.env.APOLLO_API_KEY_PROD!,
baseUrl: 'https://api.apollo.io/api/v1',
isSandbox: false,
rateLimit: { maxPerMinute: 100, concurrency: 10 },
features: { enrichment: true, sequences: true, deals: true, bulkEnrichment: true },
credits: { dailyBudget: 500, alertThreshold: 90 },
logging: { level: 'warn', redactPII: truSet up Apollo.
Apollo Observability
Overview
Comprehensive observability for Apollo.io integrations: Prometheus metrics (request count, latency, rate limits, credits), structured logging with PII redaction, OpenTelemetry tracing, and alerting rules. Tracks the metrics that matter: credit burn rate, enrichment success rate, and API health.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Prometheus Metrics
// src/observability/metrics.ts
import { Counter, Histogram, Gauge, Registry } from 'prom-client';
export const registry = new Registry();
export const requestsTotal = new Counter({
name: 'apollo_requests_total',
help: 'Total Apollo API requests by endpoint and status',
labelNames: ['endpoint', 'method', 'status'] as const,
registers: [registry],
});
export const requestDuration = new Histogram({
name: 'apollo_request_duration_seconds',
help: 'Apollo API request duration',
labelNames: ['endpoint'] as const,
buckets: [0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [registry],
});
export const rateLimitRemaining = new Gauge({
name: 'apollo_rate_limit_remaining',
help: 'Remaining requests in current rate limit window',
labelNames: ['endpoint'] as const,
registers: [registry],
});
export const creditsUsed = new Counter({
name: 'apollo_credits_used_total',
help: 'Total Apollo enrichment credits consumed',
labelNames: ['type'] as const, // 'person', 'organization', 'bulk'
registers: [registry],
});
export const enrichmentSuccessRate = new Gauge({
name: 'apollo_enrichment_success_rate',
help: 'Percentage of enrichment calls that found a match',
registers: [registry],
});
Step 2: Axios Interceptors for Auto-Collection
// src/observability/instrument.ts
import { AxiosInstance } from 'axios';
import { requestsTotal, requestDuration, rateLimitRemaining, creditsUsed } from './metrics';
const CREDIT_ENDPOINTS = ['/people/match', '/people/bulk_match', '/organizations/enrich'];
export function instrumentClient(client: AxiosInstance) {
client.interceptors.request.use((config) => {
(config as any)._startTime = Date.now();
return config;
});
client.interceptors.response.use(
(response) => {
const endpoint = response.config.url ?? 'unknown';
const duration = (Date.now() - (response.config as any)._startTime) / 1000;
requestsTotal.inc({ endpoint, method: response.config.method?.toUpperCase() ?? 'GET', status: String(response.status) });
requestDuration.observe({ endpoint }, duration);
// Rate limit tracking
const remaining = response.headers['x-rate-limit-remaining'Optimize Apollo.
Apollo Performance Tuning
Overview
Optimize Apollo.io API performance through response caching, connection pooling, bulk operations, parallel fetching, and result slimming. Key insight: search is free but slow (~500ms), enrichment costs credits — cache aggressively and batch enrichment calls.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Connection Pooling
Reuse TCP connections to avoid TLS handshake overhead on every request.
// src/apollo/optimized-client.ts
import axios from 'axios';
import https from 'https';
const httpsAgent = new https.Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 30_000,
});
export const optimizedClient = axios.create({
baseURL: 'https://api.apollo.io/api/v1',
headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
httpsAgent,
timeout: 15_000,
});
Step 2: Response Caching with Per-Endpoint TTLs
// src/apollo/cache.ts
import { LRUCache } from 'lru-cache';
// Different TTLs based on data volatility
const CACHE_TTLS: Record<string, number> = {
'/organizations/enrich': 24 * 60 * 60 * 1000, // 24h — company data rarely changes
'/people/match': 4 * 60 * 60 * 1000, // 4h — contact data changes occasionally
'/mixed_people/api_search': 15 * 60 * 1000, // 15min — search results are dynamic
'/mixed_companies/search': 30 * 60 * 1000, // 30min — company search
'/contact_stages': 60 * 60 * 1000, // 1h — stages rarely change
};
const cache = new LRUCache<string, { data: any; at: number }>({
max: 5000,
maxSize: 50 * 1024 * 1024,
sizeCalculation: (v) => JSON.stringify(v).length,
});
function cacheKey(endpoint: string, params: any): string {
return `${endpoint}:${JSON.stringify(params)}`;
}
export async function cachedRequest<T>(
endpoint: string,
requestFn: () => Promise<T>,
params: any,
): Promise<T> {
const key = cacheKey(endpoint, params);
const ttl = CACHE_TTLS[endpoint] ?? 15 * 60 * 1000;
const cached = cache.get(key);
if (cached && Date.now() - cached.at < ttl) return cached.data;
const data = await requestFn();
cache.set(key, { data, at: Date.now() });
return data;
}
export function getCacheStats() {
return { entries: cache.size, sizeBytes: cache.calculatedSize };
}
Step 3: Use Bulk Endpoints Over Single Calls
Apollo's bulk enrichment endpoint handles 10 records per call vs 1. Massive performance gain.
// src/apollo/bulk-ops.ts
import { optimizedClient } from './optimized-client';
import PQueue from 'p-queue';
const queue = new PQExecute Apollo.
Apollo Production Checklist
Overview
Comprehensive pre-production checklist for Apollo.io integrations with automated validation scripts. Covers authentication, resilience, observability, compliance, and credit management.
Prerequisites
- Valid Apollo.io API credentials
- Node.js 18+
- Completed
apollo-install-authsetup
Instructions
Step 1: Authentication & Key Management
// src/scripts/prod-checklist.ts
import axios from 'axios';
interface Check { category: string; name: string; pass: boolean; detail: string; }
const results: Check[] = [];
// 1.1 API key is set (not hardcoded)
results.push({
category: 'Auth', name: 'API key from env/secrets',
pass: !!process.env.APOLLO_API_KEY,
detail: process.env.APOLLO_API_KEY ? 'Loaded from env' : 'NOT SET',
});
// 1.2 Using x-api-key header (not query param)
const { execSync } = await import('child_process');
let usesHeader = true;
try { execSync('grep -rn "params.*api_key" src/ --include="*.ts"', { stdio: 'pipe' }); usesHeader = false; } catch {}
results.push({ category: 'Auth', name: 'x-api-key header auth', pass: usesHeader, detail: usesHeader ? 'OK' : 'Switch to x-api-key header' });
// 1.3 API key is valid
try {
const resp = await axios.get('https://api.apollo.io/api/v1/auth/health', {
headers: { 'x-api-key': process.env.APOLLO_API_KEY! },
});
results.push({ category: 'Auth', name: 'Key valid', pass: resp.data.is_logged_in, detail: resp.data.is_logged_in ? 'OK' : 'Invalid key' });
} catch (err: any) {
results.push({ category: 'Auth', name: 'Key valid', pass: false, detail: `HTTP ${err.response?.status}` });
}
// 1.4 .env gitignored
const gitIgnored = execSync('git check-ignore .env 2>/dev/null || echo NOT').toString().trim();
results.push({ category: 'Auth', name: '.env gitignored', pass: gitIgnored !== 'NOT', detail: gitIgnored !== 'NOT' ? 'OK' : 'Add .env to .gitignore' });
Step 2: Resilience & Error Handling
async function fileContains(dir: string, pattern: string): Promise<boolean> {
try { execSync(`grep -r "${pattern}" ${dir} --include="*.ts" -l`, { stdio: 'pipe' }); return true; }
catch { return false; }
}
results.push({ category: 'Resilience', name: 'Rate limiting',
pass: await fileContains('src/', 'rate-limiter\\|SlidingWindow\\|RateLimit'),
detail: 'Rate limiter implementation' });
results.push({ category: 'Resilience', name: 'Retry logic',
pass: await fileContains('src/', 'withRetry\\|withBackoff\\|exponential'),
detail: 'ExponImplement Apollo.
Apollo Rate Limits
Overview
Implement robust rate limiting and backoff for the Apollo.io API. Apollo uses fixed-window rate limiting with per-endpoint limits. Unlike hourly quotas, Apollo limits are per minute with a burst limit per second. Exceeding them returns HTTP 429.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Understand Apollo's Rate Limit Structure
Apollo's official rate limits (as of 2025):
Endpoint Category | Limit/min | Burst/sec | Notes
----------------------------+-----------+-----------+-------------------------------
People Search | 100 | 10 | /mixed_people/api_search (free)
People Enrichment | 100 | 10 | /people/match (1 credit each)
Bulk People Enrichment | 10 | 2 | /people/bulk_match (up to 10/call)
Organization Search | 100 | 10 | /mixed_companies/search
Organization Enrichment | 100 | 10 | /organizations/enrich
Contacts CRUD | 100 | 10 | /contacts/*
Sequences | 100 | 10 | /emailer_campaigns/*
Deals | 100 | 10 | /opportunities/*
Response headers on every successful call:
x-rate-limit-limit— max requests per windowx-rate-limit-remaining— requests remaining in current windowretry-after— seconds to wait (only on 429 responses)
Step 2: Build a Per-Endpoint Rate Limiter
// src/apollo/rate-limiter.ts
export class SlidingWindowLimiter {
private timestamps: number[] = [];
constructor(
private maxRequests: number = 100,
private windowMs: number = 60_000,
) {}
async acquire(): Promise<void> {
const now = Date.now();
// Remove timestamps outside the window
this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
if (this.timestamps.length >= this.maxRequests) {
const oldestInWindow = this.timestamps[0];
const waitMs = this.windowMs - (now - oldestInWindow) + 100;
console.warn(`[RateLimit] At capacity (${this.maxRequests}/${this.windowMs}ms). Waiting ${waitMs}ms`);
await new Promise((r) => setTimeout(r, waitMs));
}
this.timestamps.push(Date.now());
}
get remaining(): number {
const now = Date.now();
this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
return this.maxRequests - this.timestamps.length;
}
}
// Create limiters per endpoint category
export const limiters = {
search: new SlidingWindowLimiter(100, 60_000),
enrichment: new SlidingWindowLimiter(100, 60_000),
bulkEnrichment: new SlidingWindowLimiter(10, 60_000),
contacts: new SlidingWindowLimiter(100, 60_000),
sequencesImplement Apollo.
Apollo Reference Architecture
Overview
Production-ready reference architecture for Apollo.io integrations. Layered design with API client, service layer, background jobs, database models, CRM sync, and deals pipeline — all built around Apollo's REST API with correct endpoints and x-api-key authentication.
Prerequisites
- Apollo master API key
- Node.js 18+ with TypeScript
- PostgreSQL for data layer
- Redis for job queues
Instructions
Step 1: Architecture Diagram
┌───────────────────────────────────────────────┐
│ API Layer │ Express routes
│ POST /api/leads/search GET /api/org/:d │ POST /api/deals
├───────────────────────────────────────────────┤
│ Service Layer │ Business logic
│ LeadService EnrichService DealService │ SequenceService
├───────────────────────────────────────────────┤
│ Client Layer │ Apollo API wrapper
│ ApolloClient RateLimiter Cache │ CreditTracker
├───────────────────────────────────────────────┤
│ Background Jobs │ BullMQ queues
│ EnrichJob SyncJob StageChangeJob │ TaskCreatorJob
├───────────────────────────────────────────────┤
│ Data Layer │ Prisma/TypeORM
│ Contact Organization Deal AuditLog │
└───────────────────────────────────────────────┘
Step 2: Service Layer
// src/services/lead-service.ts
import { getApolloClient } from '../apollo/client';
import { withRetry } from '../apollo/retry';
import { cachedRequest } from '../apollo/cache';
export class LeadService {
private client = getApolloClient();
async searchPeople(params: { domains: string[]; titles?: string[]; seniorities?: string[]; page?: number }) {
return cachedRequest('/mixed_people/api_search',
() => withRetry(() => this.client.post('/mixed_people/api_search', {
q_organization_domains_list: params.domains,
person_titles: params.titles,
person_seniorities: params.seniorities,
page: params.page ?? 1, per_page: 100,
})),
params,
);
}
async enrichPerson(email: string) {
return withRetry(() => this.client.post('/people/match', { email }));
}
async enrichOrg(domain: string) {
return cachedRequest('/organizations/enrich',
() => withRetry(() => this.client.get('/organizations/enrich', { params: { domain } })),
{ domain },
);
}
}
Step 3: Deals/Opportunities Service
Apollo has a full Deals API for tracking revenue pipeline.
// src/services/deal-service.ts
export class DealService {
private client = getApolloClient();
asApply production-ready Apollo.
Apollo SDK Patterns
Overview
Production-ready patterns for Apollo.io API integration. Apollo has no official SDK — these patterns wrap the REST API (https://api.apollo.io/api/v1/) with type safety, retry logic, pagination, and bulk operations. All requests use the x-api-key header.
Prerequisites
- Completed
apollo-install-authsetup - TypeScript 5+ with strict mode
Instructions
Step 1: Type-Safe Client with Zod Validation
// src/apollo/client.ts
import axios, { AxiosInstance } from 'axios';
import { z } from 'zod';
const ConfigSchema = z.object({
apiKey: z.string().min(10, 'API key too short'),
baseURL: z.string().url().default('https://api.apollo.io/api/v1'),
timeout: z.number().default(30_000),
});
let instance: AxiosInstance | null = null;
export function getApolloClient(config?: Partial<z.input<typeof ConfigSchema>>): AxiosInstance {
if (instance) return instance;
const parsed = ConfigSchema.parse({
apiKey: config?.apiKey ?? process.env.APOLLO_API_KEY,
...config,
});
instance = axios.create({
baseURL: parsed.baseURL,
timeout: parsed.timeout,
headers: {
'Content-Type': 'application/json',
'x-api-key': parsed.apiKey,
},
});
return instance;
}
// Reset for testing
export function resetClient() { instance = null; }
Step 2: Custom Error Classes
// src/apollo/errors.ts
import { AxiosError } from 'axios';
export class ApolloApiError extends Error {
constructor(
message: string,
public statusCode: number,
public endpoint: string,
public retryable: boolean,
public requestId?: string,
) {
super(message);
this.name = 'ApolloApiError';
}
static fromAxios(err: AxiosError): ApolloApiError {
const status = err.response?.status ?? 0;
const body = err.response?.data as any;
return new ApolloApiError(
body?.message ?? err.message,
status,
err.config?.url ?? 'unknown',
[429, 500, 502, 503, 504].includes(status),
err.response?.headers?.['x-request-id'],
);
}
}
export class ApolloRateLimitError extends ApolloApiError {
constructor(
public retryAfterMs: number,
endpoint: string,
) {
super(`Rate limited on ${endpoint}`, 429, endpoint, true);
this.name = 'ApolloRateLimitError';
}
}
Step 3: Retry with Exponential Backoff
// src/apollo/retry.ts
import { ApolloApiError } from './errors';
export async function withRetry<T>(
fn: () => Promise<T>,
opts: { maxRetries?: number; baseMs?: number; maxMs?: number } = {},
): Promise<T> {
const { maxRetries = 3, baseMs = 1000, maxMs = 30_000 } = opts;
for (Apply Apollo.
Apollo Security Basics
Overview
Security best practices for Apollo.io API integrations. Apollo API keys grant broad access to 275M+ contacts — a leaked key is a serious incident. This covers key management, PII redaction, data access controls, key rotation, and audit procedures.
Prerequisites
- Valid Apollo.io API credentials
- Node.js 18+
Instructions
Step 1: Secure API Key Storage
Apollo supports two key types with different risk profiles:
- Standard key: search + enrichment only (lower risk)
- Master key: full CRM access including delete (highest risk)
// NEVER: const API_KEY = 'abc123'; // hardcoded
// NEVER: params: { api_key: key } // query string (logged in server access logs)
// ALWAYS: x-api-key header + env var or secret manager
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
async function getApiKey(): Promise<string> {
// Dev/staging: environment variable
if (process.env.APOLLO_API_KEY) return process.env.APOLLO_API_KEY;
// Production: GCP Secret Manager
const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
name: 'projects/my-project/secrets/apollo-api-key/versions/latest',
});
return version.payload?.data?.toString() ?? '';
}
# .gitignore — prevent accidental commits
.env
.env.local
.env.*.local
*.pem
secrets/
Step 2: PII Redaction for Logging
Apollo responses contain emails, phone numbers, and LinkedIn profiles. Never log raw responses in production.
// src/apollo/redact.ts
const PII_PATTERNS: [RegExp, string][] = [
[/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b/gi, '[EMAIL]'],
[/\b\+?\d{1,3}[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}\b/g, '[PHONE]'],
[/x-api-key[:\s]+["']?[\w-]+["']?/gi, 'x-api-key: [REDACTED]'],
[/linkedin\.com\/in\/[^\s"',]+/gi, 'linkedin.com/in/[REDACTED]'],
];
export function redactPII(text: string): string {
let result = text;
for (const [pattern, replacement] of PII_PATTERNS) {
result = result.replace(pattern, replacement);
}
return result;
}
// Attach as axios interceptor
client.interceptors.response.use((response) => {
if (process.env.NODE_ENV === 'production') {
// Never log raw Apollo response data in production
console.log(`[Apollo] ${response.status} ${response.config.url}`);
} else {
console.log('[Apollo]', redactPII(JSON.stringify(response.data).slice(0, 500)));
}
return response;
});
Step 3: Use Minimal Key Permissions
// src/apollo/scoped-client.ts
// Use standard keys for read-only operations, master Manage Apollo.
Apollo Upgrade Migration
Current State
!npm list axios 2>/dev/null | head -5
Overview
Plan and execute safe upgrades for Apollo.io API integrations. Apollo has made several breaking changes historically (query param auth to header auth, endpoint URL changes, new search endpoints). This covers auditing current usage, building compatibility layers, and migrating safely.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Audit Current API Usage
// src/scripts/api-audit.ts
import { execSync } from 'child_process';
interface EndpointUsage { endpoint: string; files: string[]; status: 'current' | 'deprecated'; }
const ENDPOINT_MAP = [
// Current endpoints
{ pattern: '/mixed_people/api_search', status: 'current' as const },
{ pattern: '/mixed_companies/search', status: 'current' as const },
{ pattern: '/people/match', status: 'current' as const },
{ pattern: '/people/bulk_match', status: 'current' as const },
{ pattern: '/organizations/enrich', status: 'current' as const },
{ pattern: '/contacts/search', status: 'current' as const },
{ pattern: '/emailer_campaigns', status: 'current' as const },
{ pattern: '/email_accounts', status: 'current' as const },
{ pattern: '/opportunities', status: 'current' as const },
// Deprecated patterns
{ pattern: '/people/search', status: 'deprecated' as const }, // old search endpoint
{ pattern: '/organizations/search', status: 'deprecated' as const },
{ pattern: 'api_key.*=', status: 'deprecated' as const }, // query param auth
{ pattern: 'api.apollo.io/v1', status: 'deprecated' as const }, // old base URL (should be /api/v1)
];
function auditUsage(srcDir: string = 'src'): EndpointUsage[] {
const results: EndpointUsage[] = [];
for (const ep of ENDPOINT_MAP) {
try {
const files = execSync(
`grep -rl "${ep.pattern}" ${srcDir} --include="*.ts" --include="*.js" 2>/dev/null`,
{ encoding: 'utf-8' },
).trim().split('\n').filter(Boolean);
if (files.length > 0) results.push({ endpoint: ep.pattern, files, status: ep.status });
} catch { /* no matches */ }
}
console.log('=== Apollo API Usage Audit ===');
for (const r of results) {
const icon = r.status === 'deprecated' ? 'WARN' : 'OK';
console.log(`${icon} ${r.endpoint} (${r.files.length} files)`);
r.files.forEach((f) => console.log(` ${f}`));
}
const deprecated = results.filter((r) => r.status === 'deprecated');
if (deprecated.length > 0) {
console.log(`\n${deprecated.lengtImplement Apollo.
Apollo Webhooks & Events
Overview
Build event-driven integrations with Apollo.io. Apollo does not have a native webhook system like Stripe — instead, you build real-time sync by polling the API for changes or using third-party webhook platforms (Zapier, Pipedream, Make) that trigger on Apollo events. This skill covers both approaches plus the Contact Stages and Tasks APIs for workflow automation.
Prerequisites
- Apollo account with master API key
- Node.js 18+ with Express
- For polling: a scheduler (cron, Cloud Scheduler, BullMQ)
- For webhook platforms: Zapier/Pipedream/Make account
Instructions
Step 1: Poll for Contact Changes
Since Apollo lacks native webhooks, poll the Contacts Search API for recently updated records.
// src/sync/contact-poller.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 SyncState {
lastSyncAt: string; // ISO timestamp
}
export async function pollContactChanges(state: SyncState) {
const { data } = await client.post('/contacts/search', {
sort_by_field: 'contact_updated_at',
sort_ascending: false,
per_page: 100,
});
const lastSync = new Date(state.lastSyncAt);
const newChanges = data.contacts.filter(
(c: any) => new Date(c.updated_at) > lastSync,
);
if (newChanges.length > 0) {
console.log(`Found ${newChanges.length} contact changes since ${state.lastSyncAt}`);
for (const contact of newChanges) {
await handleContactChange(contact);
}
state.lastSyncAt = new Date().toISOString();
}
return newChanges;
}
async function handleContactChange(contact: any) {
console.log(`Contact updated: ${contact.name} (${contact.email}) — stage: ${contact.contact_stage_id}`);
// Sync to your CRM, database, or notification system
}
Step 2: Track Contact Stage Changes
Apollo has a Contact Stages system. Use the List Contact Stages endpoint to get stage IDs, then filter contacts by stage.
// src/sync/stage-tracker.ts
export async function getContactStages() {
const { data } = await client.get('/contact_stages');
return data.contact_stages.map((s: any) => ({
id: s.id,
name: s.name,
order: s.display_order,
}));
}
// Find contacts that moved to a specific stage
export async function getContactsByStage(stageId: string) {
const { data } = await client.post('/contacts/search', {
contact_stage_ids: [stageId],
sort_by_field: 'contact_updated_at',
sort_ascending: false,
per_page: 50,
});
return data.contacts;
}
// Update a contact's stage
export async function updateCoReady to use apollo-pack?
Related Plugins
excel-analyst-pro
Professional financial modeling toolkit for Claude Code with auto-invoked Skills and Excel MCP integration. Build DCF models, LBO analysis, variance reports, and pivot tables using natural language.
brand-strategy-framework
A 7-part brand strategy framework for building comprehensive brand foundations - the same methodology top agencies use with Fortune 500 clients.
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
instantly-pack
Complete Instantly integration skill pack with 24 skills covering cold email, outreach automation, and lead generation. Flagship tier vendor pack.
juicebox-pack
Complete Juicebox integration skill pack with 24 skills covering people data, enrichment, contact search, and AI-powered discovery. Flagship tier vendor pack.
customerio-pack
Complete Customer.io integration skill pack with 24 skills covering marketing automation, email campaigns, SMS, push notifications, and customer journeys. Flagship tier vendor pack.