> ## Documentation Index
> Fetch the complete documentation index at: https://docs.babou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Build robust applications with proper error handling strategies

## Overview

Proper error handling is essential for building reliable applications with the Babou API. This guide covers common error scenarios and best practices for handling them gracefully.

## Error Response Structure

All API errors follow a consistent format:

```json theme={null}
{
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "hint": "Optional guidance on fixing the error"
}
```

<Tip>
  Always check the `code` field for programmatic error handling, not the `error` message which may change.
</Tip>

## Basic Error Handling

### Check Response Status

Always verify the response status before processing:

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function apiCall(url: string, options: RequestInit) {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();
      throw new ApiError(
        error.error,
        error.code,
        response.status,
        error.hint
      );
    }

    return await response.json();
  }

  class ApiError extends Error {
    constructor(
      message: string,
      public code: string,
      public status: number,
      public hint?: string
    ) {
      super(message);
      this.name = 'ApiError';
    }
  }

  // Usage
  try {
    const project = await apiCall('https://api.babou.ai/api/v1/projects', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.BABOU_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ name: 'Test Project' })
    });
    console.log('✓ Created:', project.id);
  } catch (error) {
    if (error instanceof ApiError) {
      console.error(`Error ${error.status}: ${error.message}`);
      console.error(`Code: ${error.code}`);
      if (error.hint) console.error(`Hint: ${error.hint}`);
    }
  }
  ```

  ```python Python theme={null}
  class ApiError(Exception):
      def __init__(self, message, code, status, hint=None):
          super().__init__(message)
          self.code = code
          self.status = status
          self.hint = hint

  def api_call(url, **kwargs):
      response = requests.request(url=url, **kwargs)

      if not response.ok:
          error = response.json()
          raise ApiError(
              error['error'],
              error['code'],
              response.status_code,
              error.get('hint')
          )

      return response.json()

  # Usage
  try:
      project = api_call(
          'https://api.babou.ai/api/v1/projects',
          method='POST',
          headers={
              'Authorization': f'Bearer {os.environ["BABOU_API_KEY"]}',
              'Content-Type': 'application/json'
          },
          json={'name': 'Test Project'}
      )
      print(f'✓ Created: {project["id"]}')
  except ApiError as e:
      print(f'Error {e.status}: {e.message}')
      print(f'Code: {e.code}')
      if e.hint:
          print(f'Hint: {e.hint}')
  ```
</CodeGroup>

## Handling Specific Errors

### Authentication Errors

<AccordionGroup>
  <Accordion title="UNAUTHORIZED (401)" icon="lock">
    **Cause:** Invalid or missing API key

    **Strategy:**

    ```typescript theme={null}
    try {
      return await apiCall(url, options);
    } catch (error) {
      if (error instanceof ApiError && error.code === 'UNAUTHORIZED') {
        console.error('Authentication failed');
        console.error('Check your API key at: https://babou.ai/settings');

        // Don't retry - auth errors won't resolve automatically
        throw new Error('Invalid API key - please check your credentials');
      }
      throw error;
    }
    ```

    **Prevention:**

    * Validate API key format before making requests
    * Check API key hasn't expired
    * Use environment variables, never hardcode keys
  </Accordion>

  <Accordion title="API_KEY_EXPIRED (401)" icon="clock">
    **Cause:** API key has passed expiration date

    **Strategy:**

    ```typescript theme={null}
    if (error.code === 'API_KEY_EXPIRED') {
      console.error('API key has expired');
      console.error('Generate a new key at: https://babou.ai/settings');

      // Notify user/admin to update key
      await notifyAdmin('API key expired - action required');

      throw new Error('API key expired - cannot proceed');
    }
    ```

    **Prevention:**

    * Set up key rotation schedule
    * Monitor key expiration dates
    * Use multiple keys for different environments
  </Accordion>
</AccordionGroup>

### Validation Errors

<AccordionGroup>
  <Accordion title="VALIDATION_ERROR (400)" icon="triangle-exclamation">
    **Cause:** Request parameters don't meet requirements

    **Strategy:**

    ```typescript theme={null}
    if (error.code === 'VALIDATION_ERROR') {
      console.error('Validation failed:', error.message);
      if (error.hint) {
        console.error('Hint:', error.hint);
      }

      // Log details for debugging
      logValidationError({
        endpoint: url,
        data: requestBody,
        error: error.message,
        hint: error.hint
      });

      // Don't retry - fix validation issues first
      throw new Error(`Validation failed: ${error.hint || error.message}`);
    }
    ```

    **Prevention:**

    ```typescript theme={null}
    function validateProject(name: string, description?: string) {
      const errors = [];

      if (!name || name.length < 1 || name.length > 30) {
        errors.push('Name must be 1-30 characters');
      }

      if (description && description.length > 1000) {
        errors.push('Description must be max 1000 characters');
      }

      if (errors.length > 0) {
        throw new Error(`Validation errors:\n${errors.join('\n')}`);
      }

      return true;
    }

    // Validate before API call
    validateProject(name, description);
    await createProject(name, description);
    ```
  </Accordion>
</AccordionGroup>

### Resource Errors

<AccordionGroup>
  <Accordion title="NOT_FOUND (404)" icon="magnifying-glass">
    **Cause:** Resource doesn't exist or you don't have access

    **Strategy:**

    ```typescript theme={null}
    if (error.code === 'NOT_FOUND') {
      console.warn(`Resource not found: ${resourceId}`);

      // Try to find the resource
      const resources = await listResources();
      const exists = resources.find(r => r.id === resourceId);

      if (!exists) {
        throw new Error(`Resource ${resourceId} does not exist`);
      } else {
        throw new Error(`No access to resource ${resourceId}`);
      }
    }
    ```

    **Prevention:**

    ```typescript theme={null}
    async function safeGetProject(projectId: string) {
      try {
        return await getProject(projectId);
      } catch (error) {
        if (error.code === 'NOT_FOUND') {
          // Fallback: list and find
          const { projects } = await listProjects();
          const project = projects.find(p => p.id === projectId);

          if (project) return project;

          // Suggest alternatives
          console.log('Did you mean one of these?');
          projects.slice(0, 5).forEach(p => {
            console.log(`- ${p.name} (${p.id})`);
          });
        }
        throw error;
      }
    }
    ```
  </Accordion>

  <Accordion title="CONFLICT (409)" icon="circle-exclamation">
    **Cause:** Resource state conflict

    **Strategy:**

    ```typescript theme={null}
    if (error.code === 'CONFLICT') {
      console.warn('Conflict:', error.message);

      // Check if we should wait and retry
      if (error.message.includes('already processing')) {
        console.log('Waiting for current operation to complete...');

        await new Promise(r => setTimeout(r, 10000)); // Wait 10s

        // Retry once
        try {
          return await apiCall(url, options);
        } catch (retryError) {
          // If still failing, give up
          throw new Error(`Still conflicting after retry: ${error.message}`);
        }
      }

      throw error;
    }
    ```

    **Prevention:**

    ```typescript theme={null}
    async function submitPromptSafe(
      projectId: string,
      chapterId: string,
      content: string
    ) {
      // Check current status first
      const chapter = await getChapter(projectId, chapterId);
      const latestPrompt = chapter.prompts?.[chapter.prompts.length - 1];

      if (latestPrompt && latestPrompt.status === 'processing') {
        console.log('Prompt already processing, waiting...');

        // Wait for completion
        await waitForPromptCompletion(projectId, chapterId);
      }

      // Now safe to submit
      return await submitPrompt(projectId, chapterId, content);
    }
    ```
  </Accordion>
</AccordionGroup>

### Rate Limiting

<AccordionGroup>
  <Accordion title="RATE_LIMIT_EXCEEDED (429)" icon="gauge-high">
    **Cause:** Too many requests in short period

    **Strategy:**

    ```typescript theme={null}
    async function apiCallWithRetry(url: string, options: RequestInit, maxRetries = 3) {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          return await apiCall(url, options);
        } catch (error) {
          if (error.code === 'RATE_LIMIT_EXCEEDED') {
            if (attempt === maxRetries - 1) {
              throw new Error('Rate limit exceeded after retries');
            }

            // Exponential backoff
            const delay = Math.pow(2, attempt) * 1000;
            console.log(`Rate limited, retrying in ${delay}ms...`);
            await new Promise(r => setTimeout(r, delay));

            continue;
          }

          throw error;
        }
      }
    }
    ```

    **Prevention:**

    ```typescript theme={null}
    class RateLimiter {
      private queue: Array<() => Promise<any>> = [];
      private processing = false;
      private requestsPerSecond = 10;

      async add<T>(fn: () => Promise<T>): Promise<T> {
        return new Promise((resolve, reject) => {
          this.queue.push(async () => {
            try {
              const result = await fn();
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });

          this.process();
        });
      }

      private async process() {
        if (this.processing || this.queue.length === 0) return;

        this.processing = true;

        while (this.queue.length > 0) {
          const fn = this.queue.shift()!;
          await fn();
          await new Promise(r => setTimeout(r, 1000 / this.requestsPerSecond));
        }

        this.processing = false;
      }
    }

    // Usage
    const limiter = new RateLimiter();

    for (const item of items) {
      await limiter.add(() => processItem(item));
    }
    ```
  </Accordion>
</AccordionGroup>

## Retry Strategies

### Exponential Backoff

```typescript theme={null}
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  options = {
    maxRetries: 3,
    baseDelay: 1000,
    maxDelay: 30000
  }
): Promise<T> {
  for (let attempt = 0; attempt < options.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isLastAttempt = attempt === options.maxRetries - 1;

      // Don't retry on client errors (except rate limiting)
      if (error instanceof ApiError) {
        if (error.status >= 400 && error.status < 500 && error.status !== 429) {
          throw error; // Client errors won't resolve with retry
        }
      }

      if (isLastAttempt) {
        throw error;
      }

      // Calculate delay with exponential backoff
      const delay = Math.min(
        options.baseDelay * Math.pow(2, attempt),
        options.maxDelay
      );

      console.log(`Retry ${attempt + 1}/${options.maxRetries} after ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }

  throw new Error('Max retries exceeded');
}

// Usage
const project = await retryWithBackoff(() =>
  createProject('My Project', 'Description')
);
```

### Conditional Retry

Only retry errors that might resolve:

```typescript theme={null}
function shouldRetry(error: ApiError): boolean {
  // Don't retry client errors
  if (error.status >= 400 && error.status < 500) {
    // Except rate limiting
    return error.status === 429;
  }

  // Retry server errors
  if (error.status >= 500) {
    return true;
  }

  // Retry specific codes
  const retryableCodes = ['INTERNAL_ERROR', 'UPLOAD_FAILED'];
  return retryableCodes.includes(error.code);
}

async function retryIfNeeded<T>(fn: () => Promise<T>): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (error instanceof ApiError && shouldRetry(error)) {
      console.log('Retrying after error:', error.message);
      return await retryWithBackoff(fn);
    }
    throw error;
  }
}
```

## Error Logging

### Structured Logging

```typescript theme={null}
interface ErrorLog {
  timestamp: string;
  endpoint: string;
  method: string;
  status: number;
  code: string;
  message: string;
  hint?: string;
  requestData?: any;
}

function logError(error: ApiError, context: {
  endpoint: string;
  method: string;
  requestData?: any;
}) {
  const log: ErrorLog = {
    timestamp: new Date().toISOString(),
    endpoint: context.endpoint,
    method: context.method,
    status: error.status,
    code: error.code,
    message: error.message,
    hint: error.hint,
    requestData: context.requestData
  };

  console.error(JSON.stringify(log));

  // Send to logging service
  // await sendToLogService(log);
}
```

### Error Monitoring

```typescript theme={null}
class ErrorMonitor {
  private errorCounts: Map<string, number> = new Map();
  private threshold = 5;

  track(error: ApiError) {
    const key = `${error.code}_${error.status}`;
    const count = (this.errorCounts.get(key) || 0) + 1;
    this.errorCounts.set(key, count);

    if (count >= this.threshold) {
      this.alert(error, count);
    }
  }

  private alert(error: ApiError, count: number) {
    console.error(`⚠️ Alert: ${error.code} occurred ${count} times`);
    // Send alert to monitoring service
    // await sendAlert({ error, count });
  }

  reset() {
    this.errorCounts.clear();
  }
}

const monitor = new ErrorMonitor();

try {
  await apiCall(url, options);
} catch (error) {
  if (error instanceof ApiError) {
    monitor.track(error);
  }
  throw error;
}
```

## User-Friendly Error Messages

Convert technical errors to user-friendly messages:

```typescript theme={null}
function getUserMessage(error: ApiError): string {
  const messages: Record<string, string> = {
    'UNAUTHORIZED': 'Your session has expired. Please log in again.',
    'API_KEY_EXPIRED': 'Your API access has expired. Please contact support.',
    'VALIDATION_ERROR': `Please check your input: ${error.hint || error.message}`,
    'NOT_FOUND': 'The requested item could not be found.',
    'CONFLICT': 'This operation is already in progress. Please wait.',
    'FILE_TOO_LARGE': 'The file is too large. Maximum size is 100MB.',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again in a moment.',
    'INTERNAL_ERROR': 'Something went wrong. Please try again later.'
  };

  return messages[error.code] || 'An unexpected error occurred.';
}

// Usage in UI
try {
  await uploadAsset(file);
  showSuccess('File uploaded successfully!');
} catch (error) {
  if (error instanceof ApiError) {
    showError(getUserMessage(error));
  } else {
    showError('An unexpected error occurred.');
  }
}
```

## Complete Example

Putting it all together:

```typescript theme={null}
class BabouClient {
  private baseUrl = 'https://api.babou.ai/api/v1';
  private apiKey: string;
  private rateLimiter = new RateLimiter();
  private errorMonitor = new ErrorMonitor();

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;

    return this.rateLimiter.add(() =>
      retryWithBackoff(async () => {
        try {
          const response = await fetch(url, {
            ...options,
            headers: {
              'Authorization': `Bearer ${this.apiKey}`,
              'Content-Type': 'application/json',
              ...options.headers
            }
          });

          if (!response.ok) {
            const error = await response.json();
            const apiError = new ApiError(
              error.error,
              error.code,
              response.status,
              error.hint
            );

            this.errorMonitor.track(apiError);
            logError(apiError, {
              endpoint,
              method: options.method || 'GET',
              requestData: options.body
            });

            throw apiError;
          }

          return await response.json();
        } catch (error) {
          if (error instanceof ApiError) {
            throw error;
          }
          throw new Error(`Network error: ${error.message}`);
        }
      })
    );
  }

  async createProject(name: string, description?: string) {
    // Validate first
    validateProject(name, description);

    return this.request('/projects', {
      method: 'POST',
      body: JSON.stringify({ name, description })
    });
  }

  // ... other methods
}

// Usage
const client = new BabouClient(process.env.BABOU_API_KEY!);

try {
  const project = await client.createProject('My Video');
  console.log('✓ Success:', project.id);
} catch (error) {
  console.error('✗ Failed:', getUserMessage(error));
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Error Reference" icon="book" href="/api-reference/errors">
    Complete list of error codes
  </Card>

  <Card title="Video Creation" icon="video" href="/guides/video-creation-workflow">
    Implement robust video workflows
  </Card>

  <Card title="Asset Management" icon="folder" href="/guides/asset-management">
    Handle asset upload errors
  </Card>
</CardGroup>
