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.