Skip to main content

Error Handling

Learn how to handle errors, troubleshoot issues, and write robust code with the Carbon Arc SDK.

Common Error Types

HTTP Status Codes

CodeError TypeCommon Cause
400Bad RequestInvalid parameters, malformed request
401UnauthorizedInvalid or missing API token
403ForbiddenInsufficient permissions
404Not FoundInvalid entity/insight/dataset ID
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side issue
502/503Service UnavailableAPI temporarily down

Python Exceptions

ExceptionCause
ConnectionErrorNetwork connectivity issues
TimeoutRequest took too long
JSONDecodeErrorInvalid response format
KeyErrorMissing expected data in response
ValueErrorInvalid 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

  1. Check token is correct — No extra spaces or missing characters
  2. Verify token hasn't expired — Generate a new token if needed
  3. Check environment — Ensure you're using the right host URL
  4. 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

  1. Add delays between requests
  2. Implement exponential backoff
  3. Batch operations where possible
  4. 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

ErrorQuick Fix
401 UnauthorizedCheck/regenerate API token
403 ForbiddenVerify subscription tier
400 Bad RequestCheck parameter types and formats
404 Not FoundVerify IDs exist using Ontology API
429 Rate LimitAdd delays, implement retry logic
500 Server ErrorWait and retry
ConnectionErrorCheck internet, verify host URL
TimeoutRetry, reduce query scope

Best Practices

  1. Always use try/except around API calls
  2. Implement retry logic for transient errors (429, 5xx)
  3. Validate parameters before making API calls
  4. Use logging to diagnose issues
  5. Check for empty responses before processing
  6. Use safe_get() for nested data access
  7. Cache responses when possible