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

# Error Handling

> Best practices for handling errors in production integrations with Day Copilot API

## Overview

Robust error handling is critical for production integrations. This guide covers common error scenarios, retry strategies, and best practices for building resilient applications.

## Error Response Format

All Day Copilot API errors follow a consistent format:

```json theme={null}
{
  "error": "Error Type",
  "message": "Human-readable error description",
  "status": 400,
  "correlationId": "req_abc123xyz",
  "details": {
    "field": "title",
    "constraint": "required"
  }
}
```

**Key Fields:**

* `error`: Error category (e.g., "Validation Error", "Not Found")
* `message`: Detailed explanation
* `status`: HTTP status code
* `correlationId`: Unique ID for support/debugging
* `details`: Additional context (optional)

## HTTP Status Codes

| Code  | Meaning               | Typical Cause            | Action                   |
| ----- | --------------------- | ------------------------ | ------------------------ |
| `400` | Bad Request           | Invalid parameters       | Fix request format       |
| `401` | Unauthorized          | Missing/invalid token    | Check authentication     |
| `403` | Forbidden             | Insufficient permissions | Check user access        |
| `404` | Not Found             | Resource doesn't exist   | Verify ID/check deletion |
| `409` | Conflict              | Draft conflict           | Refresh and retry        |
| `422` | Unprocessable Entity  | Validation failed        | Fix validation errors    |
| `429` | Too Many Requests     | Rate limit exceeded      | Implement backoff        |
| `500` | Internal Server Error | Server issue             | Retry with backoff       |
| `503` | Service Unavailable   | Maintenance/outage       | Retry later              |

## Common Error Scenarios

### 1. Authentication Errors (401)

**Cause:** Invalid or expired token

```json theme={null}
{
  "error": "Unauthorized",
  "message": "Missing or invalid authentication token",
  "status": 401
}
```

**Solution:**

```javascript theme={null}
async function makeAuthenticatedRequest(url, options) {
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${getToken()}`
    }
  });

  if (response.status === 401) {
    // Token expired, refresh it
    const newToken = await refreshAuthToken();

    // Retry with new token
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${newToken}`
      }
    });
  }

  return response;
}
```

### 2. Validation Errors (422)

**Cause:** Request body doesn't meet validation requirements

```json theme={null}
{
  "error": "Validation Error",
  "message": "Field 'title' is required",
  "status": 422,
  "details": {
    "field": "title",
    "constraint": "required",
    "value": null
  }
}
```

**Solution:**

```javascript theme={null}
async function createTask(data) {
  try {
    const response = await fetch('https://app.daycopilot.ai/api/v1/tasks', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    if (response.status === 422) {
      const error = await response.json();
      throw new ValidationError(error.message, error.details);
    }

    return response.json();
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error('Validation failed:', error.field, error.constraint);
      // Show user-friendly message in UI
    }
    throw error;
  }
}

class ValidationError extends Error {
  constructor(message, details) {
    super(message);
    this.name = 'ValidationError';
    this.field = details?.field;
    this.constraint = details?.constraint;
  }
}
```

### 3. Not Found Errors (404)

**Cause:** Resource doesn't exist or user lacks access (RLS)

```json theme={null}
{
  "error": "Not Found",
  "message": "Task with ID abc-123 does not exist",
  "status": 404
}
```

**Important:** For security reasons, Day Copilot returns 404 (not 403) when you lack access to a resource.

**Solution:**

```javascript theme={null}
async function getTask(taskId) {
  try {
    const response = await fetch(
      `https://app.daycopilot.ai/api/v1/tasks/${taskId}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );

    if (response.status === 404) {
      // Could be: doesn't exist, deleted, or no access
      console.warn(`Task ${taskId} not found or inaccessible`);
      return null;
    }

    return response.json();
  } catch (error) {
    console.error('Failed to fetch task:', error);
    return null;
  }
}
```

### 4. Conflict Errors (409)

**Cause:** Draft conflict - another version was published

```json theme={null}
{
  "error": "Draft conflict",
  "message": "Another draft was published while yours was being edited",
  "status": 409,
  "data": {
    "your_version": "1.2",
    "current_version": "2.0",
    "conflicting_fields": ["title", "priority"]
  }
}
```

**Solution:**

```javascript theme={null}
async function publishDraftWithRetry(taskId) {
  const MAX_RETRIES = 3;

  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      const response = await fetch(
        `https://app.daycopilot.ai/api/v1/tasks/${taskId}/draft`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ action: 'accept' })
        }
      );

      if (response.status === 409) {
        // Conflict detected
        console.log(`Conflict on attempt ${attempt + 1}, fetching latest...`);

        // Fetch current published version
        const current = await fetch(
          `https://app.daycopilot.ai/api/v1/tasks/${taskId}`,
          { headers: { 'Authorization': `Bearer ${token}` } }
        ).then(r => r.json());

        // Re-apply our changes to latest version
        await fetch(
          `https://app.daycopilot.ai/api/v1/tasks/${taskId}`,
          {
            method: 'PUT',
            headers: {
              'Authorization': `Bearer ${token}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify(ourChanges)
          }
        );

        // Retry publish
        continue;
      }

      return response.json();
    } catch (error) {
      if (attempt === MAX_RETRIES - 1) throw error;
    }
  }

  throw new Error('Failed to publish draft after retries');
}
```

### 5. Rate Limit Errors (429)

**Cause:** Too many requests in time window

```json theme={null}
{
  "error": "Rate limit exceeded",
  "message": "Too many requests",
  "status": 429,
  "retryAfter": 60
}
```

**Response Headers:**

```http theme={null}
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699564800
Retry-After: 60
```

**Solution:**

```javascript theme={null}
async function makeRequestWithRateLimit(url, options) {
  const response = await fetch(url, options);

  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After') || 60;
    console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);

    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
    return makeRequestWithRateLimit(url, options);
  }

  return response;
}
```

### 6. Server Errors (500, 503)

**Cause:** Temporary server issues or maintenance

```json theme={null}
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred",
  "status": 500,
  "correlationId": "req_abc123xyz"
}
```

**Solution:**

```javascript theme={null}
async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Retry on 5xx errors
      if (response.status >= 500 && response.status < 600) {
        if (attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          console.log(`Server error, retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }

      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

## Retry Strategies

### Exponential Backoff

```javascript theme={null}
class APIClient {
  async requestWithBackoff(url, options, maxRetries = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fetch(url, options);

        // Don't retry client errors (4xx except 429)
        if (response.status >= 400 && response.status < 500 && response.status !== 429) {
          throw new Error(await response.text());
        }

        // Retry server errors and rate limits
        if (response.status === 429 || response.status >= 500) {
          const delay = Math.min(1000 * Math.pow(2, attempt), 32000);
          console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
          await this.sleep(delay);
          continue;
        }

        return response;
      } catch (error) {
        if (attempt === maxRetries - 1) throw error;
      }
    }
  }

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

### Circuit Breaker Pattern

```javascript theme={null}
class CircuitBreaker {
  constructor(failureThreshold = 5, timeout = 60000) {
    this.failureThreshold = failureThreshold;
    this.timeout = timeout;
    this.failures = 0;
    this.state = 'CLOSED';  // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
      console.warn('Circuit breaker opened due to failures');
    }
  }
}

// Usage
const breaker = new CircuitBreaker(5, 60000);

async function makeAPICall() {
  return breaker.execute(async () => {
    return fetch('https://app.daycopilot.ai/api/v1/tasks', {
      headers: { 'Authorization': `Bearer ${token}` }
    });
  });
}
```

## Logging & Debugging

### Using Correlation IDs

```javascript theme={null}
async function makeRequest(url, options) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();
      console.error('API Error:', {
        url,
        status: response.status,
        correlationId: error.correlationId,
        message: error.message
      });

      // Save correlation ID for support ticket
      saveErrorLog({
        correlationId: error.correlationId,
        timestamp: new Date().toISOString(),
        endpoint: url,
        error: error.message
      });
    }

    return response;
  } catch (error) {
    console.error('Network error:', error);
    throw error;
  }
}
```

### Error Monitoring

```javascript theme={null}
class ErrorMonitor {
  constructor() {
    this.errors = [];
  }

  logError(error, context) {
    const entry = {
      timestamp: new Date().toISOString(),
      error: error.message,
      correlationId: error.correlationId,
      context,
      stackTrace: error.stack
    };

    this.errors.push(entry);

    // Send to monitoring service (e.g., Sentry, Datadog)
    if (typeof window !== 'undefined' && window.Sentry) {
      window.Sentry.captureException(error, {
        tags: {
          correlationId: error.correlationId
        },
        extra: context
      });
    }
  }

  getRecentErrors(count = 10) {
    return this.errors.slice(-count);
  }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always Include Error Context">
    Log enough information to debug issues:

    ```javascript theme={null}
    try {
      await createTask(data);
    } catch (error) {
      console.error('Failed to create task:', {
        error: error.message,
        correlationId: error.correlationId,
        input: data,
        timestamp: new Date().toISOString()
      });
    }
    ```
  </Accordion>

  <Accordion title="Implement Exponential Backoff">
    For transient errors (rate limits, server issues), use exponential backoff instead of immediate retries.
  </Accordion>

  <Accordion title="Distinguish Retriable vs Non-Retriable Errors">
    * **Retriable**: 429, 500, 503, network errors
    * **Non-retriable**: 400, 401, 403, 404, 422

    Don't waste resources retrying validation errors.
  </Accordion>

  <Accordion title="Use Circuit Breakers">
    Implement circuit breakers to prevent cascading failures when the API is experiencing issues.
  </Accordion>

  <Accordion title="Store Correlation IDs">
    Always save correlation IDs from error responses. They're essential for support tickets and debugging.
  </Accordion>

  <Accordion title="Graceful Degradation">
    When API calls fail, provide fallback behavior:

    ```javascript theme={null}
    async function getTasks() {
      try {
        return await fetchFromAPI();
      } catch (error) {
        console.warn('API unavailable, using cache');
        return getFromLocalCache();
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Production-Ready Error Handling

Complete example with all best practices:

```javascript theme={null}
class DayCopilotClient {
  constructor(token) {
    this.token = token;
    this.baseURL = 'https://app.daycopilot.ai/api/v1';
    this.breaker = new CircuitBreaker(5, 60000);
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const maxRetries = options.retriable !== false ? 3 : 1;

    return this.breaker.execute(async () => {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          const response = await fetch(url, {
            ...options,
            headers: {
              'Authorization': `Bearer ${this.token}`,
              'Content-Type': 'application/json',
              ...options.headers
            }
          });

          // Handle specific status codes
          if (response.status === 401) {
            await this.refreshToken();
            continue; // Retry with new token
          }

          if (response.status === 429) {
            const retryAfter = response.headers.get('Retry-After') || 60;
            await this.sleep(retryAfter * 1000);
            continue;
          }

          if (response.status >= 500) {
            if (attempt < maxRetries - 1) {
              const delay = Math.pow(2, attempt) * 1000;
              await this.sleep(delay);
              continue;
            }
          }

          if (!response.ok) {
            const error = await response.json();
            throw new APIError(error);
          }

          return response.json();
        } catch (error) {
          if (attempt === maxRetries - 1) {
            this.logError(error, { endpoint, attempt });
            throw error;
          }
        }
      }
    });
  }

  async refreshToken() {
    // Implement token refresh logic
    console.log('Refreshing authentication token...');
  }

  logError(error, context) {
    console.error('API Error:', {
      message: error.message,
      correlationId: error.correlationId,
      context
    });
  }

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

class APIError extends Error {
  constructor(errorResponse) {
    super(errorResponse.message);
    this.name = 'APIError';
    this.status = errorResponse.status;
    this.correlationId = errorResponse.correlationId;
    this.details = errorResponse.details;
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Rate Limits" icon="gauge" href="/rate-limits">
    Understand rate limiting policies
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    Learn about token management
  </Card>

  <Card title="First API Call" icon="rocket" href="/guides/first-api-call">
    Step-by-step tutorial
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Complete endpoint documentation
  </Card>
</CardGroup>
