Error Handling
Learn how to handle errors, troubleshoot issues, and write robust code with the Carbon Arc SDK.
Common Error Types
HTTP Status Codes
| Code | Error Type | Common Cause |
|---|---|---|
| 400 | Bad Request | Invalid parameters, malformed request |
| 401 | Unauthorized | Invalid or missing API token |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Invalid entity/insight/dataset ID |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side issue |
| 502/503 | Service Unavailable | API temporarily down |
Python Exceptions
| Exception | Cause |
|---|---|
ConnectionError | Network connectivity issues |
Timeout | Request took too long |
JSONDecodeError | Invalid response format |
KeyError | Missing expected data in response |
ValueError | Invalid parameter values |
Authentication Errors (401/403)
Symptoms
401 Unauthorized
403 Forbidden
Causes
- Invalid API token
- Expired credentials
- Wrong environment (prod vs dev)
- Insufficient subscription tier
Solutions
- Check token is correct — No extra spaces or missing characters
- Verify token hasn't expired — Generate a new token if needed
- Check environment — Ensure you're using the right host URL
- Verify permissions — Check your subscription includes the data
Token Validation Helper
def validate_token(token: str) -> bool:
"""Basic token format validation"""
if not token:
print("Token is empty")
return False
if token == "YOUR_API_TOKEN":
print("Token is still the placeholder")
return False
if len(token) < 20:
print("Token seems too short")
return False
if " " in token:
print("Token contains spaces - check for copy/paste errors")
return False
return True
Bad Request Errors (400)
Common Causes & Fixes
1. Invalid Entity ID
# Wrong - string instead of integer
entities=[{"carc_id": "64719", "representation": "company"}]
# Correct - integer
entities=[{"carc_id": 64719, "representation": "company"}]
2. Invalid Insight ID
# Wrong
insight="revenue"
# Correct
insight=347
3. Invalid Date Format
# Wrong format
"date_range": {"start_date": "01-01-2024", "end_date": "12-31-2024"}
# Correct format (YYYY-MM-DD)
"date_range": {"start_date": "2024-01-01", "end_date": "2024-12-31"}
4. Invalid Date Range
# Wrong - end before start
"date_range": {"start_date": "2024-12-31", "end_date": "2024-01-01"}
# Correct - start before end
"date_range": {"start_date": "2024-01-01", "end_date": "2024-12-31"}
5. Invalid Resolution
# Wrong
"date_resolution": "yearly"
# Correct - valid options: day, week, month, quarter
"date_resolution": "quarter"
6. Invalid Aggregation
# Wrong
aggregate="average"
# Correct - valid options: sum, mean, median, count, count_distinct
aggregate="mean"
Parameter Validation Helper
import re
def validate_framework_params(entities, insight, filters):
"""Validate framework parameters before API call"""
errors = []
# Validate entities
if not entities:
errors.append("entities list is empty")
else:
for i, entity in enumerate(entities):
if "carc_id" not in entity:
errors.append(f"Entity {i}: missing 'carc_id'")
elif not isinstance(entity.get("carc_id"), int):
errors.append(f"Entity {i}: 'carc_id' must be integer")
if "representation" not in entity:
errors.append(f"Entity {i}: missing 'representation'")
# Validate insight
if not isinstance(insight, int):
errors.append(f"insight must be integer, got {type(insight).__name__}")
# Validate date range
date_range = filters.get("date_range", {})
start = date_range.get("start_date", "")
end = date_range.get("end_date", "")
if start and end and start > end:
errors.append(f"start_date ({start}) is after end_date ({end})")
date_pattern = r"^\d{4}-\d{2}-\d{2}$"
if start and not re.match(date_pattern, start):
errors.append(f"start_date format invalid: {start}")
if end and not re.match(date_pattern, end):
errors.append(f"end_date format invalid: {end}")
if errors:
for error in errors:
print(f"Error: {error}")
return False
return True
Not Found Errors (404)
Causes
- Entity ID doesn't exist
- Insight ID doesn't exist or isn't available for the entity
- Dataset ID doesn't exist
- Framework ID is invalid or wasn't purchased
Solutions
Find Valid Entity IDs
entities = client.ontology.get_entities(page=1, size=100)
for entity in entities.get('items', []):
print(f"{entity['carc_id']}: {entity['carc_name']}")
Find Valid Insights for an Entity
entity_info = client.ontology.get_entity_information(
entity_id=64719,
representation="company"
)
print(f"Entity: {entity_info.get('carc_name')}")
Find Valid Dataset IDs
datasets = client.data.get_datasets()
for ds in datasets.get('datasources', []):
ds_id = ds.get('dataset_id', ['N/A'])
if isinstance(ds_id, list):
ds_id = ds_id[0]
print(f"{ds_id}: {ds.get('dataset_name')}")
ID Validation Helpers
def check_entity_exists(client, entity_id: int, representation: str) -> bool:
"""Check if an entity ID exists"""
try:
info = client.ontology.get_entity_information(
entity_id=entity_id,
representation=representation
)
print(f"Found: Entity {entity_id}: {info.get('carc_name', 'Unknown')}")
return True
except Exception as e:
if "404" in str(e):
print(f"Not found: Entity {entity_id}")
return False
def check_dataset_exists(client, dataset_id: str) -> bool:
"""Check if a dataset ID exists"""
try:
info = client.data.get_dataset_information(dataset_id=dataset_id)
print(f"Found: Dataset {dataset_id}: {info.get('dataset_name', 'Unknown')}")
return True
except Exception as e:
if "404" in str(e):
print(f"Not found: Dataset {dataset_id}")
return False
Rate Limit Errors (429)
What It Means
You've made too many API requests in a short period. The API limits requests to protect service quality.
Solutions
- Add delays between requests
- Implement exponential backoff
- Batch operations where possible
- Cache responses to avoid repeated calls
Retry with Exponential Backoff
import time
def api_call_with_retry(func, max_retries=3, base_delay=1):
"""
Execute API call with exponential backoff retry logic.
Args:
func: Function to call (lambda or callable)
max_retries: Maximum retry attempts
base_delay: Base delay in seconds (doubles each retry)
"""
last_exception = None
for attempt in range(max_retries + 1):
try:
return func()
except Exception as e:
last_exception = e
error_str = str(e)
# Retry on rate limit or server errors
if any(code in error_str for code in ["429", "500", "502", "503"]):
if attempt < max_retries:
delay = base_delay * (2 ** attempt)
print(f"Waiting {delay}s before retry {attempt + 1}...")
time.sleep(delay)
continue
# For other errors, don't retry
raise e
raise last_exception
# Usage
result = api_call_with_retry(
lambda: client.data.get_datasets(),
max_retries=3,
base_delay=1
)
Network Errors
Connection Error
Causes: No internet, firewall blocking, wrong host URL
Solutions:
- Check internet connection
- Verify host URL is correct
- Check if VPN/proxy is required
Timeout Error
Causes: Server slow to respond, request too large
Solutions:
- Retry the request
- Reduce query scope
- Try during off-peak hours
SSL/TLS Error
Causes: Outdated CA certificates, corporate proxy
Solutions:
- Update CA certificates
- Check with IT department
Safe API Call Wrapper
import requests
def safe_api_call(func):
"""Execute API call with comprehensive error handling"""
try:
result = func()
return (True, result)
except requests.exceptions.ConnectionError as e:
return (False, {
"error": "ConnectionError",
"message": "Failed to connect to API",
"suggestions": ["Check internet", "Verify host URL"]
})
except requests.exceptions.Timeout as e:
return (False, {
"error": "Timeout",
"message": "Request timed out",
"suggestions": ["Retry", "Reduce query scope"]
})
except Exception as e:
return (False, {
"error": type(e).__name__,
"message": str(e)
})
# Usage
success, result = safe_api_call(lambda: client.data.get_datasets())
if success:
print("Got data:", result)
else:
print(f"Error: {result['message']}")
Data Handling Errors
Safe Data Extraction
Avoid KeyError and IndexError with safe navigation:
def safe_get(data, *keys, default=None):
"""Safely navigate nested dictionaries"""
result = data
for key in keys:
if isinstance(result, dict):
result = result.get(key, default)
if result is default:
return default
elif isinstance(result, list) and isinstance(key, int):
try:
result = result[key]
except IndexError:
return default
else:
return default
return result
# Usage
response = {"datasets": [{"name": "Consumer Spend"}]}
# Unsafe (can raise KeyError/IndexError)
name = response["datasets"][0]["name"]
# Safe (returns default if not found)
name = safe_get(response, "datasets", 0, "name", default="Unknown")
Handle Empty Responses
def process_api_response(response, data_key="data"):
"""Process API response with proper empty checks"""
if not response:
print("Response is empty")
return []
if not isinstance(response, dict):
print(f"Unexpected type: {type(response).__name__}")
return []
data = response.get(data_key, [])
if not data:
# Try alternative keys
for alt_key in ["datasources", "items", "records", "results"]:
if alt_key in response and response[alt_key]:
return response[alt_key]
return []
return data
Comprehensive Error Handler
class CarbonArcError(Exception):
"""Custom exception for Carbon Arc API errors"""
def __init__(self, message, error_code=None, suggestions=None):
self.message = message
self.error_code = error_code
self.suggestions = suggestions or []
super().__init__(self.message)
def handle_api_error(e):
"""Parse API errors with helpful messages"""
error_str = str(e).lower()
if "401" in str(e) or "unauthorized" in error_str:
return CarbonArcError(
"Authentication failed",
error_code=401,
suggestions=["Check API token", "Verify token not expired"]
)
if "403" in str(e) or "forbidden" in error_str:
return CarbonArcError(
"Access denied",
error_code=403,
suggestions=["Check subscription tier", "Contact support"]
)
if "404" in str(e) or "not found" in error_str:
return CarbonArcError(
"Resource not found",
error_code=404,
suggestions=["Verify ID exists", "Use Ontology API to find valid IDs"]
)
if "429" in str(e) or "rate limit" in error_str:
return CarbonArcError(
"Rate limit exceeded",
error_code=429,
suggestions=["Wait and retry", "Implement exponential backoff"]
)
if "400" in str(e) or "bad request" in error_str:
return CarbonArcError(
"Bad request",
error_code=400,
suggestions=["Check parameters", "Verify date format YYYY-MM-DD"]
)
if any(code in str(e) for code in ["500", "502", "503"]):
return CarbonArcError(
"Server error",
error_code=500,
suggestions=["Wait and retry", "Contact support if persistent"]
)
return CarbonArcError(f"Unexpected error: {str(e)}")
# Usage
try:
result = client.explorer.get_framework_data(framework_id="invalid")
except Exception as e:
error = handle_api_error(e)
print(f"Error: {error.message}")
print(f"Code: {error.error_code}")
for suggestion in error.suggestions:
print(f" - {suggestion}")
Logging Setup
import logging
from datetime import datetime
def setup_logging(level=logging.INFO, log_file=None):
"""Set up logging for debugging"""
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger('carbonarc')
logger.setLevel(level)
logger.handlers = []
# Console output
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# File output (optional)
if log_file:
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
def logged_api_call(logger, name, func):
"""Execute API call with logging"""
logger.info(f"Starting: {name}")
start_time = datetime.now()
try:
result = func()
elapsed = (datetime.now() - start_time).total_seconds()
logger.info(f"Completed: {name} ({elapsed:.2f}s)")
return result
except Exception as e:
elapsed = (datetime.now() - start_time).total_seconds()
logger.error(f"Failed: {name} ({elapsed:.2f}s) - {str(e)}")
raise
# Usage
logger = setup_logging(level=logging.DEBUG, log_file="carbonarc.log")
result = logged_api_call(logger, "Get Datasets", lambda: client.data.get_datasets())
Quick Reference
Error Resolution Cheatsheet
| Error | Quick Fix |
|---|---|
| 401 Unauthorized | Check/regenerate API token |
| 403 Forbidden | Verify subscription tier |
| 400 Bad Request | Check parameter types and formats |
| 404 Not Found | Verify IDs exist using Ontology API |
| 429 Rate Limit | Add delays, implement retry logic |
| 500 Server Error | Wait and retry |
| ConnectionError | Check internet, verify host URL |
| Timeout | Retry, reduce query scope |
Best Practices
- Always use
try/exceptaround API calls - Implement retry logic for transient errors (429, 5xx)
- Validate parameters before making API calls
- Use logging to diagnose issues
- Check for empty responses before processing
- Use
safe_get()for nested data access - Cache responses when possible
Related Guides
- Explorer API — Build and purchase frameworks
- Filters & Date Ranges — Valid filter options
- ID Reference — Find valid IDs