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

# Error Handling

> Handle API errors gracefully with proper status codes and retry strategies

Understanding API error responses and implementing proper error handling is crucial for building reliable integrations with the Streamforge External API.

## HTTP Status Codes

The API uses standard HTTP status codes to indicate request outcomes:

| Code  | Meaning           | When It Occurs                                                    |
| ----- | ----------------- | ----------------------------------------------------------------- |
| `200` | Success           | Request completed successfully                                    |
| `400` | Bad Request       | Invalid parameters, missing required fields, or validation errors |
| `401` | Unauthorized      | Missing `x-api-key` header                                        |
| `403` | Forbidden         | Invalid or revoked API key                                        |
| `404` | Not Found         | Resource doesn't exist (profile, content, game)                   |
| `429` | Too Many Requests | Rate limit exceeded                                               |
| `5xx` | Server Error      | Internal server issues (retry with backoff)                       |

## Error Response Format

All error responses follow a consistent structure:

```json theme={null}
{
  "error": {
    "status": 404,
    "message": "Profile not found"
  },
  "meta": {
    "request_id": "5e7c0137-1ed3-4b4b-8e5f-3d9cf00f7ac4"
  }
}
```

The `request_id` can be used when contacting support about specific errors.

## Handling Specific Errors

### 400 Bad Request

Invalid parameters or malformed requests:

```javascript theme={null}
if (response.status === 400) {
  const error = await response.json();
  console.error('Validation error:', error.error.message);
  // Check your request parameters
}
```

**Common causes**:

* Invalid `platform` value (must be `twitch`, `youtube`, or `tiktok`)
* `limit` out of range (must be 1-50)
* Missing required fields in bulk requests
* Invalid date format in query parameters

### 401 Unauthorized

Missing API key:

```javascript theme={null}
if (response.status === 401) {
  console.error('API key is missing');
  // Add x-api-key header to your request
}
```

**Solution**: Ensure all requests include the `x-api-key` header.

### 403 Forbidden

Invalid or revoked API key:

```javascript theme={null}
if (response.status === 403) {
  console.error('API key is invalid or revoked');
  // Contact support@streamforge.com for a new key
}
```

**Common causes**:

* Typo in API key
* Key was rotated or revoked
* Key is inactive

### 404 Not Found

Resource doesn't exist:

```javascript theme={null}
if (response.status === 404) {
  const error = await response.json();
  // Resource doesn't exist - this is normal for some use cases
  // Handle gracefully (e.g., skip, log, or retry later)
}
```

**Note**: 404s are **normal** in some scenarios:

* Profile IDs that don't exist
* Content that was deleted
* Games not in the database

Handle 404s gracefully rather than treating them as errors.

### 429 Too Many Requests

Rate limit exceeded:

```javascript theme={null}
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After');
  const seconds = parseInt(retryAfter, 10);

  // Wait before retrying
  await sleep(seconds * 1000);
  // Retry the request
}
```

**Response headers**:

* `Retry-After`: Seconds to wait before retrying
* `X-RateLimit-*-remaining`: Remaining quota (often `0`)

## Retry Strategies

### Exponential Backoff

Implement exponential backoff for retryable errors (429, 5xx):

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

    if (response.ok) {
      return response;
    }

    // Don't retry 4xx errors (except 429)
    if (response.status >= 400 && response.status < 500 && response.status !== 429) {
      throw new Error(`Client error: ${response.status}`);
    }

    // Calculate backoff delay
    const delay = Math.min(1000 * Math.pow(2, attempt), 30000);

    // For 429, use Retry-After header
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      if (retryAfter) {
        await sleep(parseInt(retryAfter, 10) * 1000);
        continue;
      }
    }

    await sleep(delay);
  }

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

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

## Handling Bulk Operation Errors

Bulk operations use **partial success** - some items may be missing:

```javascript theme={null}
async function handleBulkResponse(response) {
  const data = await response.json();

  if (response.ok) {
    // Check for missing items
    if (data.meta.missing_ids && data.meta.missing_ids.length > 0) {
      console.warn(`Missing items: ${data.meta.missing_ids.join(', ')}`);
      // Handle missing items (e.g., log, retry individually, or skip)
    }

    return data.payload;
  } else {
    // Handle full request failure
    throw new Error(`Bulk request failed: ${data.error.message}`);
  }
}
```

## Best Practices

<Tip>
  Always check response status before parsing JSON. Some errors may not return JSON bodies.
</Tip>

* **Check status first**: Verify `response.ok` or `response.status` before parsing JSON
* **Handle 404s gracefully**: Missing resources are normal; don't treat as errors
* **Respect Retry-After**: For 429 errors, wait the specified time before retrying
* **Log request IDs**: Include `request_id` from error responses in your logs
* **Monitor error rates**: Track error rates to detect issues early
* **Implement circuit breakers**: Stop retrying if error rate is too high

## Example: Complete Error Handler

```javascript theme={null}
async function apiRequest(url, options) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'x-api-key': process.env.API_KEY,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    // Handle rate limiting
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      throw new RateLimitError(parseInt(retryAfter, 10));
    }

    // Parse response
    const data = await response.json();

    if (!response.ok) {
      throw new ApiError(data.error.message, response.status, data.meta.request_id);
    }

    return data;
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Handle rate limit
      await sleep(error.retryAfter * 1000);
      return apiRequest(url, options); // Retry
    }
    throw error;
  }
}

class ApiError extends Error {
  constructor(message, status, requestId) {
    super(message);
    this.status = status;
    this.requestId = requestId;
  }
}

class RateLimitError extends Error {
  constructor(retryAfter) {
    super('Rate limit exceeded');
    this.retryAfter = retryAfter;
  }
}
```

<Warning>
  Don't retry 4xx errors (except 429) - they indicate client errors that won't be fixed by retrying.
</Warning>
