Skip to main content

Error Handling

Learn how to handle errors, implement retry strategies, and build resilient applications with the Claro Python SDK.

Exception Hierarchy

The SDK provides a hierarchy of exceptions for different error types:
BaytAPIError (base class)
├── BaytAuthError (401, 403)
├── BaytNotFoundError (404)
├── BaytRateLimitError (429)
└── BaytValidationError (400)
All SDK exceptions inherit from BaytAPIError, allowing you to catch all SDK errors with a single handler.

Exception Types

BaytAuthError

Authentication and authorization errors (HTTP 401, 403):
from baytos.claro import BaytClient, BaytAuthError

try:
    client = BaytClient(api_key="invalid_key")
    prompt = client.get_prompt("@workspace/test:v1")
except BaytAuthError as e:
    print(f"Authentication failed: {e}")
    print("Please check your API key")
Common causes:
  • Invalid or expired API key
  • Insufficient permissions
  • Workspace access denied

BaytNotFoundError

Resource not found errors (HTTP 404):
from baytos.claro import BaytClient, BaytNotFoundError

client = BaytClient(api_key="your_api_key")

try:
    prompt = client.get_prompt("@workspace/nonexistent:v1")
except BaytNotFoundError as e:
    print(f"Prompt not found: {e}")
    print("Check the package name and version")
Common causes:
  • Incorrect package name
  • Wrong version number
  • Prompt deleted or moved
  • No access to the workspace

BaytRateLimitError

Rate limit exceeded (HTTP 429):
from baytos.claro import BaytClient, BaytRateLimitError
import time

client = BaytClient(api_key="your_api_key")

try:
    prompt = client.get_prompt("@workspace/test:v1")
except BaytRateLimitError as e:
    print(f"Rate limited: {e}")
    print("Waiting before retry...")
    time.sleep(60)  # Wait 1 minute
Note: The SDK automatically retries on 429 with exponential backoff. This exception is only raised after all retries are exhausted.

BaytValidationError

Invalid request parameters (HTTP 400):
from baytos.claro import BaytClient, BaytValidationError

client = BaytClient(api_key="your_api_key")

try:
    # Invalid limit
    result = client.list_prompts(limit=200)
except BaytValidationError as e:
    print(f"Validation error: {e}")
Common causes:
  • Invalid package name format
  • Out of range parameters (e.g., limit > 100)
  • Missing required fields

BaytAPIError

Base exception for all API errors:
from baytos.claro import BaytClient, BaytAPIError

client = BaytClient(api_key="your_api_key")

try:
    prompt = client.get_prompt("@workspace/test:v1")
except BaytAPIError as e:
    print(f"API error: {e}")
    # Catches all SDK errors
Includes:
  • Network errors
  • Server errors (5xx)
  • Timeout errors
  • All specific exceptions above

Basic Error Handling

Catch Specific Errors

Handle different error types appropriately:
from baytos.claro import (
    BaytClient,
    BaytAuthError,
    BaytNotFoundError,
    BaytRateLimitError,
    BaytValidationError,
    BaytAPIError
)

client = BaytClient(api_key="your_api_key")

try:
    prompt = client.get_prompt("@workspace/my-prompt:v1")
    print(f"Success: {prompt.title}")

except BaytAuthError:
    # Handle authentication errors
    print("Authentication failed. Check your API key.")
    print("Get a new key at: https://claro.baytos.ai")

except BaytNotFoundError:
    # Handle not found errors
    print("Prompt not found. Check the package name.")

except BaytRateLimitError:
    # Handle rate limiting
    print("Rate limit exceeded. Please wait and try again.")

except BaytValidationError as e:
    # Handle validation errors
    print(f"Invalid request: {e}")

except BaytAPIError as e:
    # Handle other API errors
    print(f"API error: {e}")

except Exception as e:
    # Handle unexpected errors
    print(f"Unexpected error: {e}")

Catch All SDK Errors

Use the base exception to catch all SDK errors:
from baytos.claro import BaytClient, BaytAPIError

client = BaytClient(api_key="your_api_key")

try:
    prompt = client.get_prompt("@workspace/test:v1")
except BaytAPIError as e:
    # Handles all SDK exceptions
    print(f"SDK error: {e}")
    # Log to your error tracking system

Retry Strategies

Built-in Retries

The SDK automatically retries on rate limits and server errors:
from baytos.claro import BaytClient

# Default: 3 retries with exponential backoff
client = BaytClient(api_key="your_api_key", max_retries=3)

# Increase retries for unreliable networks
client = BaytClient(api_key="your_api_key", max_retries=5)

# Disable retries (not recommended)
client = BaytClient(api_key="your_api_key", max_retries=0)
The SDK retries on:
  • 429 (Rate Limit) - Respects Retry-After header
  • 5xx (Server Errors) - Uses exponential backoff
The SDK does NOT retry on:
  • 4xx (Client Errors) - Except 429
  • Network Errors

Manual Retry Logic

Implement custom retry logic for specific operations:
import time
from baytos.claro import BaytClient, BaytAPIError

def get_prompt_with_retry(client, package_name, max_attempts=3):
    """Get prompt with manual retry logic"""

    for attempt in range(max_attempts):
        try:
            return client.get_prompt(package_name)

        except BaytAPIError as e:
            if attempt == max_attempts - 1:
                # Last attempt - raise the error
                raise

            # Wait before retrying (exponential backoff)
            wait_time = 2 ** attempt
            print(f"Error: {e}")
            print(f"Retrying in {wait_time} seconds... (attempt {attempt + 1}/{max_attempts})")
            time.sleep(wait_time)

# Usage
client = BaytClient(api_key="...")
prompt = get_prompt_with_retry(client, "@workspace/test:v1")

Retry with Decorators

Use a decorator for reusable retry logic:
import time
from functools import wraps
from baytos.claro import BaytAPIError

def retry_on_error(max_attempts=3, delay=1):
    """Decorator to retry on BaytAPIError"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except BaytAPIError as e:
                    if attempt == max_attempts - 1:
                        raise
                    wait_time = delay * (2 ** attempt)
                    time.sleep(wait_time)
            return None
        return wrapper
    return decorator

# Usage
@retry_on_error(max_attempts=3, delay=1)
def fetch_prompt(client, package_name):
    return client.get_prompt(package_name)

client = BaytClient(api_key="...")
prompt = fetch_prompt(client, "@workspace/test:v1")

Graceful Degradation

Provide fallbacks when operations fail:
from baytos.claro import BaytClient, BaytNotFoundError

def get_prompt_with_fallback(client, primary_package, fallback_package=None):
    """Try primary prompt, fall back to alternative if not found"""

    try:
        return client.get_prompt(primary_package)

    except BaytNotFoundError:
        if fallback_package:
            print(f"Primary prompt not found, using fallback: {fallback_package}")
            try:
                return client.get_prompt(fallback_package)
            except BaytNotFoundError:
                print("Fallback prompt also not found")
                raise

        raise

# Usage
client = BaytClient(api_key="...")
prompt = get_prompt_with_fallback(
    client,
    "@workspace/custom-support:v2",
    fallback_package="@workspace/generic-support:v1"
)

Logging Errors

Integrate with Python’s logging system:
import logging
from baytos.claro import BaytClient, BaytAPIError

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def fetch_prompt_safely(package_name):
    """Fetch prompt with comprehensive logging"""
    client = BaytClient(api_key="...")

    try:
        logger.info(f"Fetching prompt: {package_name}")
        prompt = client.get_prompt(package_name)
        logger.info(f"Successfully fetched: {prompt.title}")
        return prompt

    except BaytAPIError as e:
        logger.error(f"Failed to fetch prompt {package_name}: {e}")
        raise

# Usage
try:
    prompt = fetch_prompt_safely("@workspace/test:v1")
except BaytAPIError:
    # Error already logged
    pass

Error Context

Provide helpful error messages to users:
from baytos.claro import (
    BaytClient,
    BaytAuthError,
    BaytNotFoundError,
    BaytRateLimitError,
    BaytAPIError
)

def get_user_friendly_error(error):
    """Convert SDK errors to user-friendly messages"""

    if isinstance(error, BaytAuthError):
        return (
            "Authentication failed. Please check your API key. "
            "You can generate a new key at: "
            "https://claro.baytos.ai"
        )

    elif isinstance(error, BaytNotFoundError):
        return (
            "The requested prompt was not found. "
            "Please verify the package name and version."
        )

    elif isinstance(error, BaytRateLimitError):
        return (
            "You've made too many requests. "
            "Please wait a few minutes before trying again."
        )

    elif isinstance(error, BaytAPIError):
        return (
            "An error occurred while communicating with Claro. "
            "Please try again later."
        )

    else:
        return f"An unexpected error occurred: {error}"

# Usage
client = BaytClient(api_key="...")

try:
    prompt = client.get_prompt("@workspace/test:v1")
except Exception as e:
    error_message = get_user_friendly_error(e)
    print(error_message)

Complete Example

#!/usr/bin/env python3
"""
Robust error handling example
"""

import os
import sys
import logging
import time
from baytos.claro import (
    BaytClient,
    BaytAuthError,
    BaytNotFoundError,
    BaytRateLimitError,
    BaytValidationError,
    BaytAPIError
)

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def fetch_prompt_robust(package_name, max_retries=3):
    """Fetch prompt with comprehensive error handling"""

    client = BaytClient(api_key=os.getenv("BAYT_API_KEY"))

    for attempt in range(max_retries):
        try:
            logger.info(f"Fetching prompt: {package_name} (attempt {attempt + 1}/{max_retries})")
            prompt = client.get_prompt(package_name)
            logger.info(f"Successfully fetched: {prompt.title}")
            return prompt

        except BaytAuthError as e:
            logger.error(f"Authentication failed: {e}")
            logger.error("Please check your BAYT_API_KEY environment variable")
            sys.exit(1)  # Don't retry auth errors

        except BaytNotFoundError as e:
            logger.error(f"Prompt not found: {e}")
            logger.error(f"Package name: {package_name}")
            sys.exit(1)  # Don't retry not found errors

        except BaytValidationError as e:
            logger.error(f"Invalid request: {e}")
            sys.exit(1)  # Don't retry validation errors

        except BaytRateLimitError as e:
            if attempt < max_retries - 1:
                wait_time = 60 * (attempt + 1)
                logger.warning(f"Rate limited: {e}")
                logger.warning(f"Waiting {wait_time} seconds before retry...")
                time.sleep(wait_time)
            else:
                logger.error("Rate limit exceeded and max retries exhausted")
                raise

        except BaytAPIError as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt
                logger.warning(f"API error: {e}")
                logger.warning(f"Retrying in {wait_time} seconds...")
                time.sleep(wait_time)
            else:
                logger.error(f"API error after {max_retries} attempts: {e}")
                raise

        except Exception as e:
            logger.error(f"Unexpected error: {e}")
            raise

    return None

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python error_handling_example.py <package_name>")
        sys.exit(1)

    package_name = sys.argv[1]

    try:
        prompt = fetch_prompt_robust(package_name)
        if prompt:
            print(f"\nPrompt: {prompt.title}")
            print(f"Version: {prompt.version}")
            print(f"\nContent:\n{prompt.generator[:200]}...")

    except Exception as e:
        logger.error(f"Failed to fetch prompt: {e}")
        sys.exit(1)

Best Practices

Order exception handlers from most specific to most general:
try:
    prompt = client.get_prompt("@workspace/test:v1")

# ✅ Good: Specific first
except BaytNotFoundError:
    print("Not found")
except BaytAuthError:
    print("Auth error")
except BaytAPIError:
    print("Other API error")

# ❌ Bad: General first (specific handlers never reached)
except BaytAPIError:
    print("API error")
except BaytNotFoundError:
    print("Not found")  # Never reached!
Only retry on transient errors:
# ✅ Good: Retry on transient errors
if isinstance(e, (BaytRateLimitError, BaytAPIError)):
    retry()

# ❌ Bad: Retry on auth errors (will always fail)
if isinstance(e, BaytAuthError):
    retry()  # Pointless - bad key won't become valid
Always log errors with context:
import logging

logger = logging.getLogger(__name__)

try:
    prompt = client.get_prompt(package_name)
except BaytAPIError as e:
    logger.error(
        f"Failed to fetch prompt",
        extra={
            'package_name': package_name,
            'error': str(e),
            'error_type': type(e).__name__
        }
    )
    raise
Don’t expose technical errors to end users:
# ✅ Good: User-friendly message
try:
    prompt = client.get_prompt(pkg)
except BaytAuthError:
    print("We couldn't verify your account. Please check your settings.")

# ❌ Bad: Technical error message
except BaytAuthError as e:
    print(f"BaytAuthError: {e}")  # Confusing for users

Next Steps