Error Handling
Learn how to handle API errors effectively.
Error Response Format​
All errors follow this structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
],
"request_id": "req_abc123"
}
}
HTTP Status Codes​
| Code | Meaning | Description |
|---|---|---|
400 | Bad Request | Invalid request format or parameters |
401 | Unauthorized | Missing or invalid authentication |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Resource conflict (duplicate) |
422 | Unprocessable Entity | Validation failed |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server error |
503 | Service Unavailable | Temporary unavailability |
Error Codes​
Authentication Errors​
| Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid token |
TOKEN_EXPIRED | 401 | Access token has expired |
INVALID_API_KEY | 401 | API key is invalid |
API_KEY_REVOKED | 401 | API key has been revoked |
Authorization Errors​
| Code | HTTP | Description |
|---|---|---|
FORBIDDEN | 403 | Access denied |
INSUFFICIENT_PERMISSIONS | 403 | Missing required scope |
WORKSPACE_ACCESS_DENIED | 403 | No access to workspace |
Validation Errors​
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 422 | Input validation failed |
INVALID_FORMAT | 400 | Invalid data format |
MISSING_REQUIRED_FIELD | 422 | Required field missing |
INVALID_FIELD_VALUE | 422 | Field value invalid |
Resource Errors​
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Resource not found |
ALREADY_EXISTS | 409 | Resource already exists |
CONFLICT | 409 | Resource conflict |
GONE | 410 | Resource deleted |
Rate Limit Errors​
| Code | HTTP | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Rate limit exceeded |
DAILY_LIMIT_EXCEEDED | 429 | Daily quota exceeded |
Server Errors​
| Code | HTTP | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Internal server error |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
TIMEOUT | 504 | Request timeout |
Handling Errors​
TypeScript Example​
interface ApiError {
code: string;
message: string;
details?: Array<{ field: string; message: string }>;
request_id: string;
}
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: ApiError;
}
async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
...options?.headers,
},
});
const result: ApiResponse<T> = await response.json();
if (!result.success) {
throw new ApiRequestError(result.error!, response.status);
}
return result.data!;
}
class ApiRequestError extends Error {
constructor(
public error: ApiError,
public statusCode: number
) {
super(error.message);
this.name = 'ApiRequestError';
}
get isValidationError() {
return this.error.code === 'VALIDATION_ERROR';
}
get isAuthError() {
return this.statusCode === 401;
}
get isRateLimited() {
return this.statusCode === 429;
}
}
// Usage
try {
const lead = await apiRequest('/v1/leads/lead_123');
} catch (error) {
if (error instanceof ApiRequestError) {
if (error.isAuthError) {
// Refresh token or re-authenticate
} else if (error.isRateLimited) {
// Wait and retry
} else if (error.isValidationError) {
// Show validation errors to user
console.log(error.error.details);
}
}
}
Python Example​
import requests
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
@dataclass
class ApiError:
code: str
message: str
details: Optional[List[Dict[str, str]]] = None
request_id: Optional[str] = None
class ApiException(Exception):
def __init__(self, error: ApiError, status_code: int):
self.error = error
self.status_code = status_code
super().__init__(error.message)
def api_request(method: str, url: str, **kwargs) -> Dict[str, Any]:
response = requests.request(
method,
f"https://api.propertybase.ai/v1{url}",
headers={"Authorization": f"Bearer {API_KEY}"},
**kwargs
)
result = response.json()
if not result.get("success"):
error_data = result.get("error", {})
raise ApiException(
ApiError(**error_data),
response.status_code
)
return result.get("data")
# Usage
try:
lead = api_request("GET", "/leads/lead_123")
except ApiException as e:
if e.status_code == 401:
# Handle authentication error
pass
elif e.status_code == 429:
# Handle rate limit
pass
elif e.error.code == "VALIDATION_ERROR":
# Handle validation errors
for detail in e.error.details or []:
print(f"{detail['field']}: {detail['message']}")
Validation Error Details​
Validation errors include detailed field-level messages:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "price.amount",
"message": "Price must be a positive number"
},
{
"field": "bedrooms",
"message": "Must be between 1 and 20"
}
]
}
}
Debugging​
Request ID​
Every response includes a request ID for debugging:
X-Request-Id: req_abc123def456
Include this ID when contacting support.
Error Logging​
Log errors for debugging:
function logApiError(error: ApiRequestError) {
console.error('API Error:', {
code: error.error.code,
message: error.error.message,
requestId: error.error.request_id,
statusCode: error.statusCode,
timestamp: new Date().toISOString(),
});
}
Best Practices​
- Always check
success- Don't assume requests succeed - Handle specific errors - Different errors need different handling
- Log request IDs - Essential for debugging
- Show user-friendly messages - Don't expose raw error messages
- Implement retries - For transient errors (500, 503, 429)
- Validate locally first - Reduce API calls for invalid data