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

fetch
$curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/apollo-reference-architecture?format=md"
SKILL.mdapollo-reference-architecture

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
  • LeadService with cached search and retried enrichment
  • DealService with 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

LayerStrategy
ClientRetry with backoff, circuit breaker for prolonged outages
ServiceCache fallback on failure, credit budget enforcement
Jobs3 retries with exponential backoff, dead letter after max
APIStructured JSON error responses with error codes

Resources

Next Steps

Proceed to apollo-multi-env-setup for environment configuration.

┌ stats

installs/wk0
░░░░░░░░░░
github stars1.7K
██████████
first seenMar 23, 2026
└────────────

┌ repo

jeremylongshore/claude-code-plugins-plus-skills
by jeremylongshore
└────────────