Apollo reference architecture
425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill apollo-reference-architectureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
'Implement Apollo.io reference architecture.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.7 KB, as published. Nobody here has run it
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.