Skip to main content

Overview

The Bread SDK provides a comprehensive error hierarchy for handling different failure scenarios. All errors inherit from bread.APIError.

Error Hierarchy

All Bread SDK errors inherit from BreadError:
Connection failures, timeouts, network errors.Subclasses:
  • APITimeoutError - Request exceeded timeout threshold
Common causes: Network connectivity, firewall, VPN issues
HTTP error responses from the API (4xx, 5xx status codes).Subclasses:
  • BadRequestError (400) - Invalid request parameters
  • AuthenticationError (401) - Invalid or missing API key
  • PermissionDeniedError (403) - Insufficient permissions
  • NotFoundError (404) - Resource doesn’t exist
  • ConflictError (409) - Resource conflict (e.g., immutable field change)
  • UnprocessableEntityError (422) - Validation error
  • RateLimitError (429) - Too many requests
  • InternalServerError (≥500) - Server-side error
API returned data that doesn’t match expected schema.Common causes: API version mismatch, malformed response

Error Status Codes

400 - BadRequestError

Malformed request

401 - AuthenticationError

Invalid API key

403 - PermissionDeniedError

Insufficient permissions

404 - NotFoundError

Resource not found

409 - ConflictError

Resource conflict (e.g., immutable field change)

422 - UnprocessableEntityError

Invalid entity

429 - RateLimitError

Rate limit exceeded

500+ - InternalServerError

Server error

Basic Error Handling

import aibread
from aibread import Bread

client = Bread()

try:
    repos = client.repo.list()
except aibread.APIConnectionError as e:
    print("Server could not be reached")
    print(e.__cause__)  # Underlying exception
except aibread.RateLimitError as e:
    print("Rate limited - back off")
except aibread.AuthenticationError as e:
    print("Invalid API key")
except aibread.APIStatusError as e:
    print(f"Status: {e.status_code}")
    print(f"Response: {e.response}")

Specific Error Handling

Authentication Errors

import aibread
from aibread import Bread

try:
    client = Bread()
    repos = client.repo.list()
except aibread.AuthenticationError:
    print("Invalid API key - check BREAD_API_KEY environment variable")
except aibread.PermissionDeniedError:
    print("Valid key but insufficient permissions")

Not Found Errors

import aibread
from aibread import Bread

client = Bread()

try:
    repo = client.repo.get("my_repo")
except aibread.NotFoundError:
    # Create if doesn't exist
    repo = client.repo.set(repo_name="my_repo")
    print("Repository created")

Conflict Errors

import aibread
from aibread import Bread

client = Bread()

try:
    repo = client.repo.set(
        repo_name="existing_repo",
        base_model="different_model"
    )
except aibread.ConflictError:
    print("Cannot change base_model of existing repository")

Rate Limiting

import aibread
import time

def retry_with_backoff(fn, max_retries=3):
    """Retry function with exponential backoff"""
    for attempt in range(max_retries):
        try:
            return fn()
        except aibread.RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait_time = 2 ** attempt
            print(f"Rate limited, waiting {wait_time}s")
            time.sleep(wait_time)

# Usage
repos = retry_with_backoff(lambda: client.repo.list())

Connection Errors

import aibread
import time

def retry_on_connection_error(fn, max_retries=3):
    """Retry on connection failures"""
    for attempt in range(max_retries):
        try:
            return fn()
        except aibread.APIConnectionError as e:
            if attempt == max_retries - 1:
                print(f"Failed after {max_retries} attempts")
                raise
            print(f"Connection error, retry {attempt + 1}/{max_retries}")
            time.sleep(2)
        except aibread.APITimeoutError as e:
            if attempt == max_retries - 1:
                raise
            print(f"Timeout, retry {attempt + 1}/{max_retries}")
            time.sleep(2)

# Usage
repos = retry_on_connection_error(lambda: client.repo.list())

Error Properties

All APIError subclasses have:
  • message: Error message
  • request: Original request object
  • body: Response body (if available)
APIStatusError additionally has:
  • response: Full response object
  • statusCode: HTTP status code
import aibread

try:
    client.repo.get("nonexistent")
except aibread.APIStatusError as e:
    print(f"Status code: {e.status_code}")
    print(f"Response headers: {e.response.headers}")
    print(f"Response body: {e.body}")
    print(f"Request URL: {e.request.url}")

Comprehensive Error Handler

import aibread
import logging
import time

logger = logging.getLogger(__name__)

def handle_bread_error(e: Exception) -> dict:
    """Central error handler for Bread SDK"""
    if isinstance(e, aibread.AuthenticationError):
        logger.error("Authentication failed")
        return {"error": "auth_failed", "retry": False}
    
    elif isinstance(e, aibread.PermissionDeniedError):
        logger.error("Permission denied")
        return {"error": "permission_denied", "retry": False}
    
    elif isinstance(e, aibread.NotFoundError):
        logger.warning("Resource not found")
        return {"error": "not_found", "retry": False}
    
    elif isinstance(e, aibread.ConflictError):
        logger.warning("Resource conflict")
        return {"error": "conflict", "retry": False}
    
    elif isinstance(e, aibread.RateLimitError):
        logger.warning("Rate limited")
        return {"error": "rate_limited", "retry": True, "wait": 60}
    
    elif isinstance(e, aibread.APITimeoutError):
        logger.error("Request timeout")
        return {"error": "timeout", "retry": True}
    
    elif isinstance(e, aibread.APIConnectionError):
        logger.error("Connection failed")
        return {"error": "connection_failed", "retry": True}
    
    elif isinstance(e, aibread.InternalServerError):
        logger.error("Server error")
        return {"error": "server_error", "retry": True}
    
    elif isinstance(e, aibread.APIStatusError):
        logger.error(f"API error: {e.status_code}")
        return {"error": "api_error", "status": e.status_code, "retry": e.status_code >= 500}
    
    else:
        logger.error(f"Unexpected error: {e}")
        return {"error": "unknown", "retry": False}

# Usage
try:
    result = client.repo.list()
except Exception as e:
    error_info = handle_bread_error(e)
    
    if error_info["retry"]:
        # Retry logic
        time.sleep(error_info.get("wait", 5))
        result = client.repo.list()
    else:
        # Handle non-retryable error
        raise

Retry Configuration

The SDK automatically retries certain errors:
from aibread import Bread

# Default: 2 retries
client = Bread()

# Disable retries
client = Bread(max_retries=0)

# Custom retry count
client = Bread(max_retries=5)

# Per-request override
client.with_options(max_retries=3).repo.list()
Auto-retried conditions:
  • 408 Request Timeout
  • 409 Conflict
  • 429 Rate Limit
  • ≥500 Internal Server Errors
  • Connection errors

Timeout Configuration

Configure timeouts to prevent hanging:
import httpx
from aibread import Bread

# Global timeout (20 seconds)
client = Bread(timeout=20.0)

# Granular timeout control
client = Bread(
    timeout=httpx.Timeout(
        60.0,         # Total
        connect=2.0,  # Connection
        read=5.0,     # Read
        write=10.0    # Write
    )
)

# Per-request override
try:
    result = client.with_options(timeout=5.0).repo.list()
except aibread.APITimeoutError:
    print("Request timed out after 5 seconds")

Async Error Handling

Error handling works the same with async client:
import asyncio
import aibread
from aibread import AsyncBread

async def safe_api_call():
    try:
        async with AsyncBread() as client:
            repos = await client.repo.list()
            return repos
    except aibread.APIConnectionError:
        print("Connection failed")
    except aibread.AuthenticationError:
        print("Auth failed")
    except aibread.APIStatusError as e:
        print(f"API error: {e.status_code}")

asyncio.run(safe_api_call())

Best Practices

Handle specific error types before catching generic APIError
Retry on transient errors (rate limits, timeouts, server errors)
Log status codes, request details, and error messages for debugging
Authentication and permission errors are not transient - fix the root cause
For production, implement circuit breakers to prevent cascading failures
Configure timeouts based on your use case (quick requests vs. long-running jobs)

Next Steps