> apollo-reference-architecture
Implement Apollo.io reference architecture. Use when designing Apollo integrations, establishing patterns, or building production-grade sales intelligence systems. Trigger with phrases like "apollo architecture", "apollo system design", "apollo integration patterns", "apollo best practices architecture".
curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/apollo-reference-architecture?format=md"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();
async createDeal(params: {
name: string;
amount: number;
ownerId: string; // Apollo user ID
accountId?: string; // Apollo account ID
contactIds?: string[]; // Apollo contact IDs
stageId?: string; // Deal stage ID
}) {
const { data } = await this.client.post('/opportunities', {
name: params.name,
amount: params.amount,
owner_id: params.ownerId,
account_id: params.accountId,
contact_ids: params.contactIds,
opportunity_stage_id: params.stageId,
});
return { dealId: data.opportunity.id, name: data.opportunity.name };
}
async listDeals(page: number = 1) {
const { data } = await this.client.post('/opportunities/search', { page, per_page: 50 });
return data.opportunities.map((d: any) => ({
id: d.id, name: d.name, amount: d.amount,
stage: d.opportunity_stage?.name, owner: d.owner?.name,
}));
}
async getDealStages() {
const { data } = await this.client.get('/opportunity_stages');
return data.opportunity_stages.map((s: any) => ({ id: s.id, name: s.name, order: s.display_order }));
}
async updateDeal(dealId: string, updates: { amount?: number; stageId?: string }) {
await this.client.patch(`/opportunities/${dealId}`, {
amount: updates.amount,
opportunity_stage_id: updates.stageId,
});
}
}
Step 4: Background Job Processing
// src/jobs/enrichment-job.ts
import { Queue, Worker, Job } from 'bullmq';
import { LeadService } from '../services/lead-service';
const connection = { host: process.env.REDIS_HOST ?? 'localhost', port: 6379 };
export const enrichmentQueue = new Queue('apollo-enrichment', {
connection,
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 1000,
},
});
const leadService = new LeadService();
new Worker('apollo-enrichment', async (job: Job) => {
switch (job.name) {
case 'enrich-person':
return leadService.enrichPerson(job.data.email);
case 'enrich-org':
return leadService.enrichOrg(job.data.domain);
case 'bulk-search': {
const results: any[] = [];
for (const domain of job.data.domains) {
const { data } = await leadService.searchPeople({ domains: [domain] });
results.push(...data.people);
await job.updateProgress(results.length);
}
return { total: results.length };
}
}
}, { connection, concurrency: 3, limiter: { max: 50, duration: 60_000 } });
Step 5: Database Model
// src/models/contact.ts (Prisma schema excerpt)
// model Contact {
// id String @id @default(cuid())
// apolloId String @unique
// email String @unique
// name String
// title String?
// seniority String?
// phone String?
// linkedinUrl String?
// organizationId String?
// rawApolloData Json?
// enrichedAt DateTime?
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// }
// TypeORM version
import { Entity, Column, PrimaryColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('contacts')
export class Contact {
@PrimaryColumn() apolloId: string;
@Column({ unique: true }) email: string;
@Column() name: string;
@Column({ nullable: true }) title: string;
@Column({ nullable: true }) seniority: string;
@Column({ nullable: true }) phone: string;
@Column({ nullable: true }) linkedinUrl: string;
@Column({ type: 'jsonb', nullable: true }) rawApolloData: Record<string, any>;
@Column({ nullable: true }) enrichedAt: Date;
@CreateDateColumn() createdAt: Date;
@UpdateDateColumn() updatedAt: Date;
}
Step 6: API Routes
// src/api/routes.ts
import { Router } from 'express';
import { LeadService } from '../services/lead-service';
import { DealService } from '../services/deal-service';
const router = Router();
const leads = new LeadService();
const deals = new DealService();
router.post('/api/leads/search', async (req, res) => {
const { data } = await leads.searchPeople(req.body);
res.json({ leads: data.people, pagination: data.pagination });
});
router.post('/api/leads/enrich', async (req, res) => {
const { data } = await leads.enrichPerson(req.body.email);
res.json({ contact: data.person });
});
router.get('/api/organizations/:domain', async (req, res) => {
const { data } = await leads.enrichOrg(req.params.domain);
res.json({ organization: data.organization });
});
router.post('/api/deals', async (req, res) => {
const result = await deals.createDeal(req.body);
res.json(result);
});
router.get('/api/deals', async (req, res) => {
const list = await deals.listDeals(parseInt(req.query.page as string) || 1);
res.json({ deals: list });
});
export { router };
Output
- Layered architecture: API, Service, Client, Jobs, Data
LeadServicewith cached search and retried enrichmentDealServicewith create, list, update, and stage management- BullMQ background jobs for async enrichment
- Database model (Prisma + TypeORM)
- Express API routes for search, enrichment, and deals
Error Handling
| Layer | Strategy |
|---|---|
| Client | Retry with backoff, circuit breaker for prolonged outages |
| Service | Cache fallback on failure, credit budget enforcement |
| Jobs | 3 retries with exponential backoff, dead letter after max |
| API | Structured JSON error responses with error codes |
Resources
Next Steps
Proceed to apollo-multi-env-setup for environment configuration.
> related_skills --same-repo
> fathom-cost-tuning
Optimize Fathom API usage and plan selection. Trigger with phrases like "fathom cost", "fathom pricing", "fathom plan".
> fathom-core-workflow-b
Sync Fathom meeting data to CRM and build automated follow-up workflows. Use when integrating Fathom with Salesforce, HubSpot, or custom CRMs, or creating automated post-meeting email summaries. Trigger with phrases like "fathom crm sync", "fathom salesforce", "fathom follow-up", "fathom post-meeting workflow".
> fathom-core-workflow-a
Build a meeting analytics pipeline with Fathom transcripts and summaries. Use when extracting insights from meetings, building CRM sync, or creating automated meeting follow-up workflows. Trigger with phrases like "fathom analytics", "fathom meeting pipeline", "fathom transcript analysis", "fathom action items sync".
> fathom-common-errors
Diagnose and fix Fathom API errors including auth failures and missing data. Use when API calls fail, transcripts are empty, or webhooks are not firing. Trigger with phrases like "fathom error", "fathom not working", "fathom api failure", "fix fathom".