# Error Handling Guide

This guide covers error responses from the rdev API and how to handle them.

## Error Response Format

All errors follow this format:

```json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description"
  },
  "meta": {
    "request_id": "req-abc123",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}
```

## Error Codes

### Authentication Errors (4xx)

| Code | HTTP Status | Description | Resolution |
|------|-------------|-------------|------------|
| `UNAUTHORIZED` | 401 | Missing or invalid API key | Check API key header |
| `KEY_REVOKED` | 401 | API key has been revoked | Request new key |
| `KEY_EXPIRED` | 401 | API key has expired | Request new key |
| `FORBIDDEN` | 403 | Insufficient permissions | Use key with required scope |
| `IP_NOT_ALLOWED` | 403 | IP not in allowlist | Use allowed IP or update key |

### Resource Errors (4xx)

| Code | HTTP Status | Description | Resolution |
|------|-------------|-------------|------------|
| `BAD_REQUEST` | 400 | Invalid request body | Check request format |
| `NOT_FOUND` | 404 | Resource not found | Verify resource ID |
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded | Wait and retry |

### Server Errors (5xx)

| Code | HTTP Status | Description | Resolution |
|------|-------------|-------------|------------|
| `INTERNAL_ERROR` | 500 | Server error | Retry later, contact support |
| `SERVICE_UNAVAILABLE` | 503 | Service not ready | Wait for service to be ready |

## Handling Errors by Type

### Authentication Errors

```javascript
async function handleAuthError(response) {
  const { error } = await response.json();

  switch (error.code) {
    case 'UNAUTHORIZED':
      // Key is missing or invalid
      throw new Error('Invalid API key. Check your configuration.');

    case 'KEY_REVOKED':
      // Key was revoked by admin
      throw new Error('API key was revoked. Request a new key.');

    case 'KEY_EXPIRED':
      // Key has expired
      throw new Error('API key expired. Request a new key.');

    case 'FORBIDDEN':
      // Key lacks required scope
      throw new Error(`Insufficient permissions: ${error.message}`);

    case 'IP_NOT_ALLOWED':
      // IP not in allowlist
      throw new Error('Your IP is not allowed for this API key.');

    default:
      throw new Error(error.message);
  }
}
```

### Rate Limiting

```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get('X-RateLimit-Reset');
      const waitMs = retryAfter
        ? (parseInt(retryAfter) * 1000) - Date.now()
        : 1000 * Math.pow(2, i); // Exponential backoff

      console.log(`Rate limited. Waiting ${waitMs}ms...`);
      await new Promise(resolve => setTimeout(resolve, waitMs));
      continue;
    }

    return response;
  }

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

### Validation Errors

```javascript
async function handleValidationError(response) {
  const { error } = await response.json();

  // Error message contains field-specific info
  // e.g., "prompt: is required"
  // e.g., "command: contains dangerous characters"

  const match = error.message.match(/^(\w+): (.+)$/);
  if (match) {
    const [, field, message] = match;
    return {
      field,
      message,
    };
  }

  return { message: error.message };
}
```

## Error Handling Best Practices

### 1. Always Check Status Code

```javascript
const response = await fetch(url, options);

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

return response.json();
```

### 2. Use Custom Error Class

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

  isRetryable() {
    return this.status >= 500 || this.status === 429;
  }

  isAuthError() {
    return this.status === 401 || this.status === 403;
  }
}
```

### 3. Log Request IDs

```javascript
async function logError(response, error) {
  const requestId = error.meta?.request_id;
  console.error(`Request ${requestId} failed:`, error.error);

  // Include in bug reports
  return {
    requestId,
    error: error.error,
    url: response.url,
    timestamp: new Date().toISOString(),
  };
}
```

### 4. Implement Circuit Breaker

```javascript
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failures = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.lastFailure = null;
  }

  async execute(fn) {
    if (this.isOpen()) {
      throw new Error('Circuit breaker is open');
    }

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

  isOpen() {
    if (this.failures < this.threshold) return false;
    if (Date.now() - this.lastFailure > this.timeout) {
      this.reset();
      return false;
    }
    return true;
  }

  recordFailure() {
    this.failures++;
    this.lastFailure = Date.now();
  }

  reset() {
    this.failures = 0;
    this.lastFailure = null;
  }
}
```

## Common Error Scenarios

### Missing API Key

```bash
curl http://localhost:8080/projects

# Response: 401
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing API key"
  }
}
```

**Fix**: Add the `X-API-Key` header.

### Invalid Command

```bash
curl -X POST http://localhost:8080/projects/test/shell \
  -H "X-API-Key: rdev_xxx" \
  -d '{"command": "rm -rf /"}'

# Response: 400
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "destructive rm command not allowed"
  }
}
```

**Fix**: Use safe commands. See security documentation for allowed patterns.

### Project Not Found

```bash
curl http://localhost:8080/projects/nonexistent \
  -H "X-API-Key: rdev_xxx"

# Response: 404
{
  "error": {
    "code": "NOT_FOUND",
    "message": "project not found: nonexistent"
  }
}
```

**Fix**: Check project ID. List projects to see available ones.

### Rate Limited

```bash
# After too many requests
# Response: 429
{
  "error": {
    "code": "TOO_MANY_REQUESTS",
    "message": "Rate limit exceeded"
  }
}
```

**Fix**: Wait for `X-RateLimit-Reset` timestamp, then retry.
