apollo-common-errors
SKILL.md
Apollo Common Errors
Overview
Comprehensive guide to diagnosing and fixing common Apollo.io API errors with specific solutions and prevention strategies.
Error Reference
401 Unauthorized
Symptoms:
{
"error": "Unauthorized",
"message": "Invalid API key"
}
Causes:
- Missing API key in request
- Invalid or expired API key
- API key revoked by admin
- Wrong API key (sandbox vs production)
Solutions:
# Verify API key is set
echo $APOLLO_API_KEY | head -c 10
# Test API key directly
curl -s "https://api.apollo.io/v1/auth/health?api_key=$APOLLO_API_KEY" | jq
# Check key in Apollo dashboard
# Settings > Integrations > API > View/Regenerate Key
Prevention:
// Validate API key on startup
async function validateApiKey() {
try {
await apollo.healthCheck();
console.log('Apollo API key valid');
} catch (error) {
console.error('Invalid Apollo API key - check APOLLO_API_KEY');
process.exit(1);
}
}
403 Forbidden
Symptoms:
{
"error": "Forbidden",
"message": "You don't have permission to access this resource"
}
Causes:
- API feature not available in plan
- User role doesn't have access
- IP restriction blocking request
- Attempting to access another account's data
Solutions:
// Check plan features before calling
const PLAN_FEATURES = {
basic: ['people_search', 'organization_enrich'],
professional: ['sequences', 'bulk_operations'],
enterprise: ['advanced_search', 'custom_fields'],
};
function checkFeatureAccess(feature: string, plan: string): boolean {
return PLAN_FEATURES[plan]?.includes(feature) ?? false;
}
422 Unprocessable Entity
Symptoms:
{
"error": "Unprocessable Entity",
"message": "q_organization_domains must be an array"
}
Causes:
- Invalid request body format
- Missing required fields
- Wrong data types
- Invalid enum values
Common Fixes:
// WRONG: String instead of array
const wrong = { q_organization_domains: 'apollo.io' };
// CORRECT: Array format
const correct = { q_organization_domains: ['apollo.io'] };
// WRONG: Number instead of string
const wrong2 = { per_page: '25' };
// CORRECT: Number type
const correct2 = { per_page: 25 };
Validation Helper:
import { z } from 'zod';
const PeopleSearchSchema = z.object({
q_organization_domains: z.array(z.string()).optional(),
person_titles: z.array(z.string()).optional(),
page: z.number().int().positive().default(1),
per_page: z.number().int().min(1).max(100).default(25),
});
function validateSearchParams(params: unknown) {
return PeopleSearchSchema.parse(params);
}
429 Too Many Requests (Rate Limited)
Symptoms:
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please retry after 60 seconds."
}
Rate Limits:
| Endpoint | Limit | Window |
|---|---|---|
| People Search | 100 req/min | 1 minute |
| Enrichment | 100 req/min | 1 minute |
| Sequences | 50 req/min | 1 minute |
| Bulk Operations | 10 req/min | 1 minute |
Solution - Exponential Backoff:
class RateLimitHandler {
private retryAfter = 0;
private retryCount = 0;
private maxRetries = 5;
async executeWithRetry<T>(fn: () => Promise<T>): Promise<T> {
while (this.retryCount < this.maxRetries) {
try {
if (this.retryAfter > 0) {
await this.wait(this.retryAfter);
this.retryAfter = 0;
}
return await fn();
} catch (error: any) {
if (error.response?.status === 429) {
this.retryAfter = this.parseRetryAfter(error.response);
this.retryCount++;
console.warn(`Rate limited, retry ${this.retryCount} after ${this.retryAfter}ms`);
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
private parseRetryAfter(response: any): number {
const retryHeader = response.headers['retry-after'];
if (retryHeader) {
return parseInt(retryHeader) * 1000;
}
return Math.pow(2, this.retryCount) * 1000; // Exponential backoff
}
private wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
500 Internal Server Error
Symptoms:
{
"error": "Internal Server Error",
"message": "An unexpected error occurred"
}
Causes:
- Apollo service outage
- Malformed request causing server error
- Timeout on complex queries
Solutions:
# Check Apollo status
curl -s https://status.apollo.io/api/v2/status.json | jq '.status.description'
# Simplify query and retry
curl -X POST "https://api.apollo.io/v1/people/search" \
-H "Content-Type: application/json" \
-d '{"api_key": "'$APOLLO_API_KEY'", "page": 1, "per_page": 1}'
Empty Results
Symptoms:
{
"people": [],
"pagination": { "total_entries": 0 }
}
Causes:
- Too restrictive filters
- Invalid domain or company name
- No matching data in Apollo database
Diagnostic Steps:
async function diagnoseEmptyResults(criteria: any) {
// Test each filter individually
const tests = [
{ name: 'domain', params: { q_organization_domains: criteria.domains } },
{ name: 'titles', params: { person_titles: criteria.titles } },
{ name: 'location', params: { person_locations: criteria.locations } },
];
for (const test of tests) {
if (test.params[Object.keys(test.params)[0]]) {
const result = await apollo.searchPeople({ ...test.params, per_page: 1 });
console.log(`${test.name}: ${result.pagination.total_entries} results`);
}
}
}
Error Handling Pattern
// src/lib/apollo/error-handler.ts
import { AxiosError } from 'axios';
export class ApolloErrorHandler {
handle(error: AxiosError): never {
const status = error.response?.status;
const data = error.response?.data as any;
switch (status) {
case 401:
throw new ApolloAuthError(
'Invalid API key. Verify APOLLO_API_KEY is set correctly.'
);
case 403:
throw new ApolloPermissionError(
`Permission denied: ${data?.message || 'Check your plan features'}`
);
case 422:
throw new ApolloValidationError(
`Invalid request: ${data?.message}`,
data?.errors
);
case 429:
throw new ApolloRateLimitError(
'Rate limit exceeded',
this.parseRetryAfter(error)
);
case 500:
throw new ApolloServerError(
'Apollo server error. Check status.apollo.io'
);
default:
throw new ApolloError(
`Apollo API error: ${status} - ${data?.message || error.message}`
);
}
}
private parseRetryAfter(error: AxiosError): number {
return parseInt(error.response?.headers['retry-after'] || '60');
}
}
Resources
Next Steps
Proceed to apollo-debug-bundle for collecting debug evidence.