| name | apollo-cost-tuning |
| description | Optimize Apollo.io costs and credit usage.
Use when managing Apollo credits, reducing API costs,
or optimizing subscription usage.
Trigger with phrases like "apollo cost", "apollo credits",
"apollo billing", "reduce apollo costs", "apollo usage".
|
| allowed-tools | Read, Write, Edit, Bash(gh:*), Bash(curl:*) |
| version | 1.13.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","apollo","api","cost-optimization"] |
| compatibility | Designed for Claude Code |
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
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) ?? ;
}
(): {
.() >= .;
}
(): {
used = .();
;
}
}
creditTracker = (
(process.. ?? , ),
);
Step 3: Deduplicate Before Enriching
import { LRUCache } from 'lru-cache';
const enrichedCache = new LRUCache<string, boolean>({
max: 50_000,
ttl: 30 * 24 * 60 * 60 * 1000,
});
export function enrichmentKey(params: { email?: string; linkedin_url?: string;
first_name?: string; last_name?: string; organization_domain?: string }): string {
return params.email
?? params.linkedin_url
?? `${params.first_name}:${params.last_name}:${params.organization_domain}`;
}
export function isAlreadyEnriched(key: string): boolean {
return enrichedCache.has(key);
}
export function markEnriched() {
enrichedCache.(key, );
}
Step 4: Score Leads Before Enriching
Only spend credits on leads worth contacting.
interface LeadSignals {
seniority?: string;
title?: string;
companyEmployees?: number;
hasEmail: boolean;
hasPhone: boolean;
hasLinkedIn: boolean;
}
export function shouldEnrich(signals: LeadSignals, threshold: number = 40): boolean {
let score = 0;
const topSeniority = ['c_suite', 'vp', 'founder', 'owner'];
if (topSeniority.includes(signals.seniority ?? '')) score += 40;
else if (signals.seniority === 'director') score += 30;
else if (signals.seniority === 'manager') score += 15;
else score += 5;
if (signals.companyEmployees && signals. >= && signals. <= ) score += ;
(signals. && signals. > ) score += ;
(!signals.) score += ;
(!signals.) score += ;
score >= threshold;
}
Step 5: Budget-Aware API Client
import axios from 'axios';
import { creditTracker } from './credit-tracker';
import { isAlreadyEnriched, markEnriched, enrichmentKey } from './dedup';
const client = axios.create({
baseURL: 'https://api.apollo.io/api/v1',
headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
});
const CREDIT_ENDPOINTS = ['/people/match', '/people/bulk_match', '/organizations/enrich'];
client.interceptors.request.use((config) => {
const isCreditEndpoint = CREDIT_ENDPOINTS.some((ep) => config.url?.includes(ep));
if (isCreditEndpoint && creditTracker.isOverBudget()) {
throw new Error(`Daily credit budget exceeded (${creditTracker.report()})`);
}
return config;
});
client...( {
isCreditEndpoint = .( response..?.(ep));
(isCreditEndpoint) {
matchCount = response.?.?. ?? ;
creditTracker.(matchCount);
email = response.?.?.;
(email) (email);
}
response;
});
{ client budgetClient };
Step 6: Cost-Optimized Pipeline
import { budgetClient } from './cost/budget-client';
import { shouldEnrich } from './cost/lead-scorer';
import { isAlreadyEnriched, enrichmentKey } from './cost/dedup';
import { creditTracker } from './cost/credit-tracker';
async function enrichHighValueLeads(people: any[]) {
let enriched = 0, skipped = 0, deduped = 0;
const toEnrich: any[] = [];
for (const person of people) {
const key = enrichmentKey({ email: person.email, linkedin_url: person.linkedin_url,
first_name: person.first_name, last_name: person.last_name });
if (isAlreadyEnriched(key)) { deduped++; continue; }
if (!shouldEnrich({ seniority: person.seniority, hasEmail: !!person.email,
hasPhone: false, hasLinkedIn: !!person.linkedin_url })) { skipped++; ; }
toEnrich.(person);
}
( i = ; i < toEnrich.; i += ) {
batch = toEnrich.(i, i + );
budgetClient.(, {
: batch.( ({
: p., : p.,
: p.?.,
})),
});
enriched += batch.;
}
.();
.();
}
Output
- Credit model reference table (free vs paid operations)
CreditTracker with daily budget enforcement
- LRU deduplication preventing double-enrichment charges
- Lead scoring to enrich only high-value contacts
- Budget-aware client blocking requests at daily limit
- Cost-optimized pipeline combining all strategies
Error Handling
| Issue | Resolution |
|---|
| Budget exceeded | Increase APOLLO_DAILY_CREDIT_BUDGET or wait until tomorrow |
| High dedup misses | Extend LRU TTL, verify key generation logic |
| Enriching low-value leads | Lower the shouldEnrich threshold |
| Month-end credit crunch | Spread enrichment evenly with daily budgets |
Resources
Next Steps
Proceed to apollo-reference-architecture for architecture patterns.