| name | apollo-rate-limits |
| description | Implement Apollo.io rate limiting and backoff.
Use when handling rate limits, implementing retry logic,
or optimizing API request throughput.
Trigger with phrases like "apollo rate limit", "apollo 429",
"apollo throttling", "apollo backoff", "apollo request limits".
|
| allowed-tools | Read, Grep, Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Apollo Rate Limits
Overview
Implement robust rate limiting and backoff strategies for Apollo.io API to maximize throughput while avoiding 429 errors.
Apollo Rate Limits
| Endpoint Category | Rate Limit | Window | Burst Limit |
|---|
| People Search | 100/min | 1 minute | 10/sec |
| Person Enrichment | 100/min | 1 minute | 10/sec |
| Organization Enrichment | 100/min | 1 minute | 10/sec |
| Sequences/Campaigns | 50/min | 1 minute | 5/sec |
| Bulk Operations | 10/min | 1 minute | 2/sec |
| General API | 100/min | 1 minute | 10/sec |
Rate Limit Headers
curl -I -X POST "https://api.apollo.io/v1/people/search" \
-H "Content-Type: application/json" \
-d '{"api_key": "'$APOLLO_API_KEY'", "per_page": 1}'
Implementation: Rate Limiter Class
interface RateLimiterConfig {
maxRequests: number;
windowMs: number;
minSpacingMs: number;
}
class RateLimiter {
private queue: Array<{
resolve: (value: void) => void;
reject: (error: Error) => void;
}> = [];
private requestTimestamps: number[] = [];
private lastRequestTime = 0;
private processing = false;
constructor(private config: RateLimiterConfig) {}
async acquire(): Promise<void> {
return new Promise((resolve, reject) => {
this.queue.push({ resolve, reject });
this.processQueue();
});
}
private () {
(. || .. === ) ;
. = ;
(.. > ) {
now = .();
. = ..(
now - ts < ..
);
(.. >= ..) {
oldestTs = .[];
waitTime = .. - (now - oldestTs) + ;
.(waitTime);
;
}
timeSinceLastRequest = now - .;
(timeSinceLastRequest < ..) {
.(.. - timeSinceLastRequest);
}
item = ..()!;
..(.());
. = .();
item.();
}
. = ;
}
(: ): <> {
( (resolve, ms));
}
}
apolloRateLimiter = ({
: ,
: ,
: ,
});
Implementation: Exponential Backoff
interface BackoffConfig {
initialDelayMs: number;
maxDelayMs: number;
maxRetries: number;
multiplier: number;
jitter: boolean;
}
const defaultConfig: BackoffConfig = {
initialDelayMs: 1000,
maxDelayMs: 60000,
maxRetries: 5,
multiplier: 2,
jitter: true,
};
export async function withBackoff<T>(
fn: () => Promise<T>,
config: Partial<BackoffConfig> = {}
): Promise<T> {
const cfg = { ...defaultConfig, ...config };
let lastError: Error;
let delay = cfg.initialDelayMs;
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
try {
await apolloRateLimiter.acquire();
return await fn();
} (: ) {
lastError = error;
status = error.?.;
(status === || status === || status === ) {
error;
}
(attempt === cfg.) {
;
}
retryAfter = error.?.?.[];
(retryAfter) {
delay = (retryAfter) * ;
}
jitter = cfg. ? .() * : ;
actualDelay = .(delay + jitter, cfg.);
.();
( (r, actualDelay));
delay *= cfg.;
}
}
lastError!;
}
Implementation: Request Queue
import PQueue from 'p-queue';
export const apolloQueue = new PQueue({
concurrency: 5,
interval: 1000,
intervalCap: 10,
});
async function batchSearchPeople(domains: string[]): Promise<Person[]> {
const results = await Promise.all(
domains.map((domain) =>
apolloQueue.add(() =>
withBackoff(() => apollo.searchPeople({ q_organization_domains: [domain] }))
)
)
);
return results.flat().map((r) => r?.people || []).flat();
}
Usage Patterns
Pattern 1: Simple Rate-Limited Request
import { withBackoff } from './backoff';
const people = await withBackoff(() =>
apollo.searchPeople({
q_organization_domains: ['stripe.com'],
per_page: 100,
})
);
Pattern 2: Batch Processing with Queue
import { apolloQueue } from './request-queue';
async function enrichCompanies(domains: string[]) {
const results = [];
for (const domain of domains) {
const result = await apolloQueue.add(
() => withBackoff(() => apollo.enrichOrganization(domain)),
{ priority: 1 }
);
results.push(result);
}
return results;
}
Pattern 3: Priority Queue for Interactive vs Background
async function interactiveSearch(query: string) {
return apolloQueue.add(
() => withBackoff(() => apollo.searchPeople({ q_keywords: query })),
{ priority: 0 }
);
}
async function backgroundSync(contacts: string[]) {
return Promise.all(
contacts.map((id) =>
apolloQueue.add(
() => withBackoff(() => apollo.getContact(id)),
{ priority: 10 }
)
)
);
}
Monitoring Rate Limit Usage
class RateLimitMonitor {
private requests: Array<{ timestamp: number; remaining: number }> = [];
recordRequest(remaining: number) {
this.requests.push({
timestamp: Date.now(),
remaining,
});
const cutoff = Date.now() - 5 * 60 * 1000;
this.requests = this.requests.filter((r) => r.timestamp > cutoff);
}
getStats() {
const lastMinute = this.requests.filter(
(r) => r.timestamp > Date.now() - 60000
);
return {
requestsLastMinute: lastMinute.length,
currentRemaining: lastMinute[lastMinute. - ]?. ?? ,
: (lastMinute. / ) * ,
: lastMinute. > ,
};
}
}
rateLimitMonitor = ();
Output
- Rate limiter class with token bucket algorithm
- Exponential backoff with jitter
- Request queue with concurrency control
- Priority-based request scheduling
- Rate limit monitoring and alerts
Error Handling
| Scenario | Strategy |
|---|
| 429 response | Use Retry-After header |
| Burst limit hit | Add minimum spacing |
| Sustained limit | Queue with concurrency |
| Network timeout | Exponential backoff |
Resources
Next Steps
Proceed to apollo-security-basics for API security best practices.