# How to Handle Errors

Learn how to handle API errors robustly.

### Problem

You want your application to handle errors gracefully without breaking the user experience.

### Solution

Implement error handling at 3 levels: validation, HTTP response, and retry.

### 1. Validation Before Sending

Avoid errors by validating data first:

```javascript
function validateRequest(query, tenantId) {
  if (!query || query.trim() === '') {
    throw new Error('Query is required');
  }

  if (!tenantId) {
    throw new Error('tenant_id is required');
  }

  return true;
}
```

### 2. HTTP Code Handling

```javascript
async function apiRequest(endpoint, body, apiKey) {
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey
    },
    body: JSON.stringify(body)
  });

  if (!response.ok) {
    const error = await response.json();

    switch (response.status) {
      case 400:
        throw new ValidationError(error.detail);

      case 404:
        throw new NotFoundError(error.detail);

      case 429:
        throw new RateLimitError(
          error.detail,
          response.headers.get('X-RateLimit-Reset')
        );

      case 500:
      case 503:
        throw new ServerError(error.detail);

      default:
        throw new ApiError(error.detail, response.status);
    }
  }

  return response.json();
}
```

### 3. Custom Error Classes

```javascript
class ApiError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
  }
}

class ValidationError extends ApiError {
  constructor(message) {
    super(message, 400);
    this.name = 'ValidationError';
  }
}

class NotFoundError extends ApiError {
  constructor(message) {
    super(message, 404);
    this.name = 'NotFoundError';
  }
}

class RateLimitError extends ApiError {
  constructor(message, resetTime) {
    super(message, 429);
    this.name = 'RateLimitError';
    this.resetTime = resetTime;
  }
}

class ServerError extends ApiError {
  constructor(message) {
    super(message, 500);
    this.name = 'ServerError';
  }
}
```

### 4. Retry with Exponential Backoff

For temporary errors (429, 500, 503):

```javascript
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const shouldRetry =
        error instanceof RateLimitError ||
        error instanceof ServerError;

      if (!shouldRetry || attempt === maxRetries - 1) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s
      const waitTime = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;

      console.log(`Retry ${attempt + 1}/${maxRetries} in ${waitTime}ms`);
      await sleep(waitTime + jitter);
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

### 5. Complete Usage

```javascript
async function searchWithErrorHandling(query, tenantId, apiKey) {
  try {
    // Validation
    validateRequest(query, tenantId);

    // Request with retry
    const result = await retryWithBackoff(() =>
      apiRequest('https://api-integrations.snackprompt.com/v1/kb/search', {
        query,
        filters: { tenant_id: tenantId },
        limit: 5
      }, apiKey)
    );

    return result;

  } catch (error) {
    // Specific handling by type
    if (error instanceof ValidationError) {
      showUserMessage('Please fill in all fields');
    } else if (error instanceof NotFoundError) {
      showUserMessage('No results found');
    } else if (error instanceof RateLimitError) {
      showUserMessage('Too many requests. Please wait.');
    } else if (error instanceof ServerError) {
      showUserMessage('Service temporarily unavailable');
    } else {
      showUserMessage('Unexpected error. Please try again.');
      console.error('Unexpected error:', error);
    }

    // Re-throw for logging or upper-level handling
    throw error;
  }
}
```

### Python: Error Handling

```python
import requests
import time
import random

class ApiError(Exception):
    def __init__(self, message, status_code):
        super().__init__(message)
        self.status_code = status_code

def api_request(endpoint, body, api_key, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.post(
                endpoint,
                headers={'x-api-key': api_key},
                json=body
            )

            if response.ok:
                return response.json()

            error = response.json()

            if response.status_code == 400:
                raise ValueError(f"Validation error: {error['detail']}")

            if response.status_code == 404:
                raise FileNotFoundError(error['detail'])

            if response.status_code == 429:
                if attempt < max_retries - 1:
                    wait = (2 ** attempt) + random.uniform(0, 1)
                    time.sleep(wait)
                    continue
                raise ApiError("Rate limit exceeded", 429)

            if response.status_code >= 500:
                if attempt < max_retries - 1:
                    wait = (2 ** attempt) + random.uniform(0, 1)
                    time.sleep(wait)
                    continue
                raise ApiError("Server error", response.status_code)

            raise ApiError(error['detail'], response.status_code)

        except requests.exceptions.ConnectionError:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            raise

    raise ApiError("Max retries exceeded", 0)
```

### Common Errors and Solutions

| Error                   | Cause              | Solution                     |
| ----------------------- | ------------------ | ---------------------------- |
| `tenant_id is required` | Missing tenant\_id | Add tenant\_id to filters    |
| `query is required`     | Empty query        | Validate before sending      |
| `Rate limit exceeded`   | Too many requests  | Implement retry with backoff |
| `Internal server error` | Server error       | Automatic retry              |
| `Service unavailable`   | Maintenance        | Wait and try again           |

### Error Logging

```javascript
function logError(error, context) {
  console.error({
    timestamp: new Date().toISOString(),
    error: {
      name: error.name,
      message: error.message,
      status: error.status
    },
    context
  });

  // Send to monitoring service
  // sendToMonitoring(error, context);
}
```

### Related

* [Error Codes](/bring-your-data-into-ai/reference/error-codes.md)
* [Endpoints](/bring-your-data-into-ai/reference/endpoints.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.snackprompt.com/bring-your-data-into-ai/how-to/how-to-handle-errors.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
