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, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
const BASE = 'https://api.apollo.io/api/v1';
const mockServer = setupServer(
http.post(`${BASE}/mixed_people/api_search`, () =>
HttpResponse.json({
people: [{ id: '1', name: 'Jane Doe', title: 'VP Sales' }],
pagination: { page: 1, per_page: 25, total_entries: 1, total_pages: 1 },
}),
),
http.post(`${BASE}/people/match`, () =>
HttpResponse.json({
person: { id: '1', name: 'Jane Doe', email: 'jane@test.com', title: 'VP Sales' },
}),
),
http.get(`${BASE}/auth/health`, () =>
HttpResponse.json({ is_logged_in: true }),
),
);
beforeAll(() => mockServer.listen());
afterEach(() => mockServer.resetHandlers());
afterAll(() => mockServer.close());
describe('People Search', () => {
it('returns contacts from search', async () => {
const { searchPeople } = await import('../workflows/lead-search');
const result = await searchPeople({ domains: ['test.com'] });
expect(result.people).toHaveLength(1);
expect(result.people[0].name).toBe('Jane Doe');
});
it('handles 429 errors', async () => {
mockServer.use(
http.post(`${BASE}/mixed_people/api_search`, () =>
HttpResponse.json({ message: 'Rate limited' }, { status: 429 }),
),
);
const { searchPeople } = await import('../workflows/lead-search');
await expect(searchPeople({ domains: ['test.com'] })).rejects.toThrow();
});
});
Step 4: Integration Tests (Live API)
// src/__tests__/integration/apollo-live.test.ts
import { describe, it, expect } from 'vitest';
import axios from 'axios';
const SKIP = !process.env.APOLLO_API_KEY;
const client = axios.create({
baseURL: 'https://api.apollo.io/api/v1',
headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
});
describe.skipIf(SKIP)('Apollo Live Integration', () => {
it('searches for people at apollo.io', async () => {
const { data } = await client.post('/mixed_people/api_search', {
q_organization_domains_list: ['apollo.io'],
per_page: 5,
});
expect(data.people.length).toBeGreaterThan(0);
});
it('enriches organization by domain', async () => {
const { data } = await client.get('/organizations/enrich', { params: { domain: 'apollo.io' } });
expect(data.organization).toBeDefined();
expect(data.organization.name).toContain('Apollo');
});
});
Output
- GitHub Actions workflow with lint, typecheck, unit test, integration test, and secret scan jobs
- MSW mock server for zero-API-call unit tests in PRs
- Live integration tests gated to main branch pushes only
- Apollo health check step before integration tests
- Secret scanning to prevent API key commits
Error Handling
| Issue |
Resolution |
| Secret not found |
gh secret list to verify, re-add with gh secret set |
| Rate limited in CI |
Unit tests use MSW, integration tests run only on main |
| Health check fails |
Check status.apollo.io; skip flaky on outage |
| Hardcoded key found |
Secret scan job fails the build; rotate the key immediately |
Resources
Next Steps
Proceed to apollo-deploy-integration for deployment configuration.
1---2name: apollo-ci-integration3description: |4 Configure Apollo.io CI/CD integration.5 Use when setting up automated testing, continuous integration,6 or deployment pipelines for Apollo integrations.7 Trigger with phrases like "apollo ci", "apollo github actions",8 "apollo pipeline", "apollo ci/cd", "apollo automated tests".9allowed-tools: Read, Write, Edit, Bash(gh:*), Bash(curl:*)10version: 1.0.011license: MIT12author: Jeremy Longshore <jeremy@intentsolutions.io>13compatible-with: claude-code, codex, openclaw14tags: [saas, apollo, deployment, testing, ci-cd]1516---1718# Apollo CI Integration1920## Overview21Set 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.2223## Prerequisites24- GitHub repository with Actions enabled25- Apollo master API key + sandbox token26- Node.js 18+2728## Instructions2930### Step 1: Store Secrets in GitHub31```bash32# Master API key for integration tests (main branch only)33gh secret set APOLLO_API_KEY --body "$APOLLO_API_KEY"3435# Sandbox token for staging tests (safe, no credits)36gh secret set APOLLO_SANDBOX_KEY --body "$APOLLO_SANDBOX_KEY"37```3839### Step 2: GitHub Actions Workflow40```yaml41# .github/workflows/apollo-ci.yml42name: Apollo CI43on:44 push:45 branches: [main]46 pull_request:47 branches: [main]4849jobs:50 lint-and-typecheck:51 runs-on: ubuntu-latest52 steps:53 - uses: actions/checkout@v454 - uses: actions/setup-node@v455 with: { node-version: '20', cache: 'npm' }56 - run: npm ci57 - run: npm run lint58 - run: npm run typecheck5960 unit-tests:61 runs-on: ubuntu-latest62 steps:63 - uses: actions/checkout@v464 - uses: actions/setup-node@v465 with: { node-version: '20', cache: 'npm' }66 - run: npm ci67 - run: npm test68 # Unit tests use MSW mocks — zero API calls6970 integration-tests:71 runs-on: ubuntu-latest72 if: github.event_name == 'push' && github.ref == 'refs/heads/main'73 needs: [unit-tests]74 steps:75 - uses: actions/checkout@v476 - uses: actions/setup-node@v477 with: { node-version: '20', cache: 'npm' }78 - run: npm ci79 - name: Apollo Health Check80 env:81 APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}82 run: |83 STATUS=$(curl -s -o /dev/null -w "%{http_code}" \84 -H "x-api-key: $APOLLO_API_KEY" \85 "https://api.apollo.io/api/v1/auth/health")86 echo "Apollo API: HTTP $STATUS"87 [ "$STATUS" = "200" ] || exit 188 - run: npm run test:integration89 env:90 APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}9192 secret-scan:93 runs-on: ubuntu-latest94 steps:95 - uses: actions/checkout@v496 - name: Check for hardcoded API keys97 run: |98 if grep -rn 'x-api-key.*[a-zA-Z0-9]\{20,\}' src/ --include='*.ts' --include='*.js'; then99 echo "Potential hardcoded API key found!"100 exit 1101 fi102 echo "No hardcoded secrets"103```104105### Step 3: MSW-Based Unit Tests106```typescript107// src/__tests__/apollo.test.ts108import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';109import { setupServer } from 'msw/node';110import { http, HttpResponse } from 'msw';111112const BASE = 'https://api.apollo.io/api/v1';113const mockServer = setupServer(114 http.post(`${BASE}/mixed_people/api_search`, () =>115 HttpResponse.json({116 people: [{ id: '1', name: 'Jane Doe', title: 'VP Sales' }],117 pagination: { page: 1, per_page: 25, total_entries: 1, total_pages: 1 },118 }),119 ),120 http.post(`${BASE}/people/match`, () =>121 HttpResponse.json({122 person: { id: '1', name: 'Jane Doe', email: 'jane@test.com', title: 'VP Sales' },123 }),124 ),125 http.get(`${BASE}/auth/health`, () =>126 HttpResponse.json({ is_logged_in: true }),127 ),128);129130beforeAll(() => mockServer.listen());131afterEach(() => mockServer.resetHandlers());132afterAll(() => mockServer.close());133134describe('People Search', () => {135 it('returns contacts from search', async () => {136 const { searchPeople } = await import('../workflows/lead-search');137 const result = await searchPeople({ domains: ['test.com'] });138 expect(result.people).toHaveLength(1);139 expect(result.people[0].name).toBe('Jane Doe');140 });141142 it('handles 429 errors', async () => {143 mockServer.use(144 http.post(`${BASE}/mixed_people/api_search`, () =>145 HttpResponse.json({ message: 'Rate limited' }, { status: 429 }),146 ),147 );148 const { searchPeople } = await import('../workflows/lead-search');149 await expect(searchPeople({ domains: ['test.com'] })).rejects.toThrow();150 });151});152```153154### Step 4: Integration Tests (Live API)155```typescript156// src/__tests__/integration/apollo-live.test.ts157import { describe, it, expect } from 'vitest';158import axios from 'axios';159160const SKIP = !process.env.APOLLO_API_KEY;161const client = axios.create({162 baseURL: 'https://api.apollo.io/api/v1',163 headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },164});165166describe.skipIf(SKIP)('Apollo Live Integration', () => {167 it('searches for people at apollo.io', async () => {168 const { data } = await client.post('/mixed_people/api_search', {169 q_organization_domains_list: ['apollo.io'],170 per_page: 5,171 });172 expect(data.people.length).toBeGreaterThan(0);173 });174175 it('enriches organization by domain', async () => {176 const { data } = await client.get('/organizations/enrich', { params: { domain: 'apollo.io' } });177 expect(data.organization).toBeDefined();178 expect(data.organization.name).toContain('Apollo');179 });180});181```182183## Output184- GitHub Actions workflow with lint, typecheck, unit test, integration test, and secret scan jobs185- MSW mock server for zero-API-call unit tests in PRs186- Live integration tests gated to main branch pushes only187- Apollo health check step before integration tests188- Secret scanning to prevent API key commits189190## Error Handling191| Issue | Resolution |192|-------|------------|193| Secret not found | `gh secret list` to verify, re-add with `gh secret set` |194| Rate limited in CI | Unit tests use MSW, integration tests run only on main |195| Health check fails | Check [status.apollo.io](https://status.apollo.io); skip flaky on outage |196| Hardcoded key found | Secret scan job fails the build; rotate the key immediately |197198## Resources199- [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)200- [Apollo Sandbox Testing](https://docs.apollo.io/docs/test-api-key)201- [MSW (Mock Service Worker)](https://mswjs.io/)202203## Next Steps204Proceed to `apollo-deploy-integration` for deployment configuration.205