API Error Reference¶
Complete reference for all API error codes, meanings, and resolution strategies to handle errors gracefully in your integration.
Error Response Format¶
Resource-specific errors (orders, blacklists, authentication) return a flat JSON object:
{
"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 |
Error Format Variations
The Enrichment Order, Blacklist, and authentication errors documented below use the flat format shown above, which matches the actual API responses. The general HTTP status sections (400, 401, 403, etc.) further down this page show a nested format with an error wrapper object -- these are illustrative examples for generic error handling patterns.
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 |
Enrichment Order Errors¶
These errors are specific to the Enrichment Order API.
Order Not Found (404)¶
Returned when the order ID does not exist or belongs to a different organization.
{
"code": "ORDER_NOT_FOUND",
"message": "Order with identifier 00000000-0000-0000-0000-000000000000 not found",
"resource_type": "Order",
"identifier": "00000000-0000-0000-0000-000000000000",
"order_id": "00000000-0000-0000-0000-000000000000"
}
Resolution: Verify the order_id from your create order response. Orders are scoped to your organization.
Order Validation Errors (422)¶
Order creation uses Pydantic validation. Errors follow this format:
{
"detail": [
{
"type": "error_type",
"loc": ["body", "field_name"],
"msg": "Human-readable message",
"input": "the_invalid_value"
}
]
}
Common validation errors:
| Error | loc |
Cause | Fix |
|---|---|---|---|
too_short |
["body", "companies"] |
Empty companies list | Provide at least 1 company |
enum |
["body", "features", 0] |
Invalid feature name | Use: COMPANY_DATA, CONTACTS, TECHNOLOGIES, JOBS, HANDELSREGISTER, ANALYSIS, EMAIL_DISCOVERY, EMAIL_GENERATION |
missing |
["body", "companies", 0, "company_name"] |
Company without a name | Every company needs company_name |
value_error |
["body"] |
Duplicate features | Remove duplicates from features array |
value_error |
["body"] |
Both profile_id and profile_name |
Provide one or the other, not both |
Insufficient Permissions (403)¶
Returned when your API key lacks the required scopes.
{
"code": "AUTHORIZATION_ERROR",
"message": "Insufficient permissions. Required scopes: orders:write",
"required_scopes": ["orders:write"],
"user_scopes": ["orders:read"]
}
Resolution: Create a new API key with both orders:read and orders:write scopes in Settings > Developer > API Keys.
Blacklist Errors¶
These errors are specific to the Blacklist Management API.
Blacklist Not Found (404)¶
Returned when the blacklist ID does not exist or belongs to a different organization.
{
"code": "BLACKLIST_NOT_FOUND",
"message": "Blacklist with identifier 00000000-0000-0000-0000-000000000000 not found",
"resource_type": "Blacklist",
"identifier": "00000000-0000-0000-0000-000000000000",
"blacklist_id": "00000000-0000-0000-0000-000000000000"
}
Resolution: Verify the blacklist_id. Blacklists are scoped to your organization.
Blacklist Access Denied (403)¶
Returned when you attempt to access a blacklist that belongs to a different organization.
{
"code": "BLACKLIST_ACCESS_DENIED",
"message": "Access denied to blacklist 00000000-0000-0000-0000-000000000000",
"blacklist_id": "00000000-0000-0000-0000-000000000000"
}
Resolution: You can only manage blacklists within your own organization. Verify the blacklist ID.
Blacklist Validation Errors (422)¶
Blacklist operations use Pydantic validation. Common errors:
| Error | loc |
Cause | Fix |
|---|---|---|---|
too_short |
["body", "entries"] |
Empty entries list | Provide at least 1 entry |
too_long |
["body", "entries"] |
More than 10,000 entries | Split into multiple requests |
missing |
["body", "entries", 0, "company_name"] |
Entry without a name | Every entry needs company_name |
string_too_short |
["body", "name"] |
Empty blacklist name | Provide a non-empty name |
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