API Error Reference¶
Complete reference for all API error codes, meanings, and resolution strategies to handle errors gracefully in your integration.
🚨 Error Response Format¶
All API errors follow a consistent format:
{
"code": "ENTITY_NOT_FOUND",
"message": "Prospect with identifier ps_abc123 not found",
"entity_type": "prospect",
"identifier": "ps_abc123"
}
Error Response Fields¶
| Field | Description | Always Present |
|---|---|---|
code |
Machine-readable error code | ✅ Yes |
message |
Human-readable error description | ✅ Yes |
| Additional fields | Context-specific details (e.g., entity_type, identifier) |
❌ Optional |
🔴 Client Errors (4xx)¶
400 Bad Request¶
Invalid request syntax or parameters:
{
"error": {
"code": "INVALID_REQUEST",
"message": "The request body is invalid",
"details": {
"validation_errors": [
{
"field": "email",
"error": "Invalid email format",
"provided": "not-an-email"
},
{
"field": "qualification_score",
"error": "Must be between 0 and 10",
"provided": 15
}
]
}
}
}
Common Causes & Solutions:
| Error Code | Cause | Solution |
|---|---|---|
INVALID_JSON |
Malformed JSON body | Validate JSON syntax |
MISSING_FIELD |
Required field absent | Check API docs for required fields |
INVALID_TYPE |
Wrong data type | Ensure correct types (string, number, etc.) |
INVALID_ENUM |
Invalid enum value | Use only allowed values |
INVALID_FORMAT |
Wrong format (date, email) | Follow format specifications |
Python Example - Handling Validation:
try:
response = requests.post(url, json=data)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 400:
error = e.response.json()['error']
if error['code'] == 'INVALID_REQUEST':
for validation_error in error['details']['validation_errors']:
print(f"Fix {validation_error['field']}: {validation_error['error']}")
401 Unauthorized¶
Authentication required or failed:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required",
"details": {
"reason": "token_expired",
"expired_at": "2024-01-20T09:00:00Z",
"suggestion": "Refresh your access token"
}
}
}
Authentication Error Codes:
| Code | Description | Resolution |
|---|---|---|
TOKEN_EXPIRED |
Access token expired | Refresh token or re-authenticate |
TOKEN_INVALID |
Malformed or corrupted token | Re-authenticate |
TOKEN_REVOKED |
Token was revoked | Re-authenticate with new credentials |
NO_TOKEN |
Missing Authorization header | Include Authorization: Bearer TOKEN |
INSUFFICIENT_SCOPE |
Token lacks required permissions | Request additional scopes |
Auto-Retry Pattern:
def make_authenticated_request(url, data, retry=True):
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401 and retry:
# Refresh token
new_token = refresh_access_token()
headers['Authorization'] = f'Bearer {new_token}'
# Retry once
return make_authenticated_request(url, data, retry=False)
raise
403 Forbidden¶
Authorized but lacks permission:
{
"error": {
"code": "FORBIDDEN",
"message": "You don't have permission to access this resource",
"details": {
"resource": "prospect_search",
"resource_id": "ps_123",
"required_permission": "prospect_search:write",
"your_permissions": ["prospect_search:read"]
}
}
}
Permission Error Types:
| Code | Scenario | Solution |
|---|---|---|
RESOURCE_ACCESS_DENIED |
Not your resource | Only access your own data |
PLAN_LIMIT_EXCEEDED |
Feature not in plan | Upgrade plan |
TEAM_PERMISSION_REQUIRED |
Need team role | Contact admin for access |
API_DISABLED |
API access disabled | Enable in settings |
404 Not Found¶
Resource doesn't exist:
{
"error": {
"code": "NOT_FOUND",
"message": "The requested resource was not found",
"details": {
"resource_type": "prospect",
"resource_id": "pr_nonexistent",
"suggestion": "The prospect may have been deleted or the ID is incorrect"
}
}
}
Not Found Scenarios:
| Resource | Common Causes | How to Check |
|---|---|---|
| Prospect | Deleted or wrong ID | List all prospects first |
| Search | Completed or expired | Check search status |
| Task | Already processed | Verify task state |
| Export | Expired download | Request new export |
409 Conflict¶
Request conflicts with current state:
{
"error": {
"code": "CONFLICT",
"message": "The request conflicts with the current state",
"details": {
"conflict_type": "duplicate_entry",
"existing_resource": {
"type": "webhook",
"id": "wh_123",
"name": "CRM Sync"
},
"suggestion": "A webhook with this URL already exists"
}
}
}
Conflict Types:
| Code | Description | Resolution |
|---|---|---|
DUPLICATE_ENTRY |
Resource already exists | Use existing or choose different identifier |
STATE_CONFLICT |
Invalid state transition | Check current state first |
CONCURRENT_MODIFICATION |
Resource modified elsewhere | Retry with latest version |
RESOURCE_LOCKED |
Resource being processed | Wait and retry |
422 Unprocessable Entity¶
Valid syntax but semantic errors:
{
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "The request is valid but cannot be processed",
"details": {
"reason": "business_logic_violation",
"specifics": "Cannot generate emails without contact information",
"missing_data": ["contact.email", "contact.first_name"],
"suggestion": "Run email discovery first"
}
}
}
Business Logic Errors:
| Code | Scenario | Fix |
|---|---|---|
MISSING_PREREQUISITES |
Required data absent | Complete prior steps |
INVALID_STATE_TRANSITION |
Workflow violation | Follow correct sequence |
BUSINESS_RULE_VIOLATION |
Rule check failed | Adjust request parameters |
INSUFFICIENT_DATA |
Not enough information | Enrich data first |
🔴 Server Errors (5xx)¶
500 Internal Server Error¶
Unexpected server error:
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred",
"details": {
"request_id": "req_abc123",
"suggestion": "Please try again. If the problem persists, contact support with the request_id"
}
}
}
Recovery Strategy:
def resilient_request(url, data, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.post(url, json=data)
if response.status_code >= 500:
if attempt < max_retries - 1:
wait = (2 ** attempt) + random.random()
time.sleep(wait)
continue
response.raise_for_status()
return response.json()
except Exception as e:
if attempt == max_retries - 1:
raise
resilient_request(url, data)
502 Bad Gateway¶
Upstream service error:
{
"error": {
"code": "BAD_GATEWAY",
"message": "Upstream service unavailable",
"details": {
"service": "email_validation",
"suggestion": "The email validation service is temporarily unavailable"
}
}
}
503 Service Unavailable¶
Service temporarily unavailable:
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable",
"details": {
"reason": "maintenance",
"expected_duration": "30m",
"status_page": "https://status.openprospect.io"
}
}
}
504 Gateway Timeout¶
Request timeout:
{
"error": {
"code": "GATEWAY_TIMEOUT",
"message": "The request timed out",
"details": {
"timeout_seconds": 30,
"suggestion": "Large requests may take longer. Try with fewer records or use async endpoints"
}
}
}
🛠️ Error Handling Best Practices¶
1. Comprehensive Error Handler¶
class OpenProspectAPIError(Exception):
def __init__(self, response):
self.response = response
error_data = response.json() # Error object directly, not wrapped
self.code = error_data.get('code', 'UNKNOWN')
self.message = error_data.get('message', 'Unknown error')
self.status_code = response.status_code
super().__init__(self.message)
def handle_api_response(response):
"""Universal API response handler"""
try:
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
error_code = e.response.json().get('code')
# Handle specific errors
if e.response.status_code == 401:
if error_code == 'AUTHENTICATION_ERROR':
# Invalid or expired API key - can't auto-refresh
raise OpenProspectAPIError(e.response)
else:
raise OpenProspectAPIError(e.response)
elif e.response.status_code == 503:
# Handle service unavailable - retry with backoff
return retry_with_backoff()
elif e.response.status_code >= 500:
# Retry server errors with backoff
return retry_with_backoff()
else:
# Client errors (4xx) - don't retry
raise OpenProspectAPIError(e.response)
2. Error Recovery Patterns¶
# Idempotent retry wrapper
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=10),
retry=retry_if_exception_type(requests.exceptions.RequestException)
)
def safe_api_call(endpoint, data):
response = requests.post(endpoint, json=data)
response.raise_for_status()
return response.json()
# Circuit breaker pattern
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.failure_count = 0
self.last_failure_time = None
self.state = 'closed' # closed, open, half-open
def call(self, func, *args, **kwargs):
if self.state == 'open':
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = 'half-open'
else:
raise Exception("Circuit breaker is open")
try:
result = func(*args, **kwargs)
if self.state == 'half-open':
self.state = 'closed'
self.failure_count = 0
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'open'
raise
3. Error Logging¶
import logging
logger = logging.getLogger('openprospect_api')
def log_api_error(error):
"""Log API errors with context"""
logger.error(
f"API Error: {error.code} - {error.message}",
extra={
'request_id': error.request_id,
'error_code': error.code,
'error_details': error.details,
'status_code': error.response.status_code
}
)
# Log to monitoring service
if error.response.status_code >= 500:
send_to_monitoring_service({
'error': error.code,
'request_id': error.request_id,
'severity': 'high'
})
📊 Common Error Scenarios¶
Webhook Delivery Errors¶
{
"error": {
"code": "WEBHOOK_DELIVERY_FAILED",
"message": "Failed to deliver webhook",
"details": {
"webhook_id": "wh_123",
"attempt": 3,
"max_attempts": 5,
"last_error": "Connection timeout",
"next_retry": "2024-01-20T11:30:00Z",
"suggestion": "Check your webhook endpoint is accessible"
}
}
}
AI Processing Errors¶
{
"error": {
"code": "AI_PROCESSING_FAILED",
"message": "AI analysis failed",
"details": {
"reason": "insufficient_data",
"missing_fields": ["company.description", "company.website_content"],
"suggestion": "Ensure company has been crawled successfully"
}
}
}
Data Quality Errors¶
{
"error": {
"code": "DATA_QUALITY_ERROR",
"message": "Data quality check failed",
"details": {
"issues": [
{
"field": "email",
"issue": "invalid_format",
"value": "notanemail"
},
{
"field": "phone",
"issue": "invalid_country_code",
"value": "+00 123456"
}
],
"suggestion": "Fix data quality issues before proceeding"
}
}
}
🚀 Quick Reference¶
HTTP Status to Action Mapping¶
| Status | Action | Retry? |
|---|---|---|
| 400 | Fix request | ❌ No |
| 401 | Check API key | ✅ Once |
| 403 | Check permissions | ❌ No |
| 404 | Verify resource | ❌ No |
| 409 | Resolve conflict | ❌ No |
| 422 | Fix business logic | ❌ No |
| 500 | Retry with backoff | ✅ Yes |
| 502 | Retry with backoff | ✅ Yes |
| 503 | Retry with backoff | ✅ Yes |
| 504 | Use async endpoint | ⚠️ Maybe |
💡 Pro Tips¶
- Store Your API Key Securely: Use environment variables or secret management
- Don't Retry Client Errors (4xx): Fix the request instead
- Implement Backoff for Server Errors (5xx): Use exponential backoff
- Monitor Error Patterns: Track errors to identify integration issues
- Use Async for Large Operations: Avoid timeouts on long-running tasks
Need Help? Contact support at support@openprospect.io