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

# API Error Reference

> Complete reference of HTTP API error responses and status codes.

This reference documents HTTP API error responses, their meanings, and how to handle them in your integration.

## Response Format

All API errors follow a consistent JSON format:

```json theme={null}
{
  "error": "error_type",
  "message": "Human-readable description",
  "code": "ERROR_CODE",
  "details": {}
}
```

For validation errors:

```json theme={null}
{
  "error": "validation_error",
  "issues": [
    {
      "kind": "validation",
      "type": "required",
      "expected": "string",
      "received": "undefined",
      "message": "Required field",
      "path": [{ "type": "object", "key": "email" }]
    }
  ]
}
```

## HTTP Status Codes

### 2xx Success

| Status | Meaning    | Description                            |
| ------ | ---------- | -------------------------------------- |
| 200    | OK         | Request succeeded                      |
| 201    | Created    | Resource created successfully          |
| 204    | No Content | Request succeeded, no content returned |

### 4xx Client Errors

| Status | Meaning              | Description                          |
| ------ | -------------------- | ------------------------------------ |
| 400    | Bad Request          | Invalid request format or parameters |
| 401    | Unauthorized         | Authentication required or invalid   |
| 403    | Forbidden            | Authenticated but not authorized     |
| 404    | Not Found            | Resource doesn't exist               |
| 409    | Conflict             | Resource state conflict              |
| 422    | Unprocessable Entity | Validation failed                    |
| 429    | Too Many Requests    | Rate limit exceeded                  |

### 5xx Server Errors

| Status | Meaning               | Description              |
| ------ | --------------------- | ------------------------ |
| 500    | Internal Server Error | Unexpected server error  |
| 502    | Bad Gateway           | Upstream service error   |
| 503    | Service Unavailable   | Temporary unavailability |
| 504    | Gateway Timeout       | Upstream service timeout |

## 400 Bad Request

Returned when the request is malformed or contains invalid data.

**Example Response**:

```json theme={null}
{
  "error": "bad_request",
  "message": "Invalid JSON in request body"
}
```

**Common Causes**:

* Malformed JSON
* Missing required fields
* Invalid field types
* Invalid parameter values

**Resolution**:

```typescript theme={null}
try {
  await fetch("/api/experiences", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
} catch (error) {
  if (error.status === 400) {
    console.error("Invalid request:", error.message);
    // Fix request data and retry
  }
}
```

## 401 Unauthorized

Returned when authentication is missing or invalid.

**Example Response**:

```json theme={null}
{
  "error": "unauthorized",
  "message": "Authentication required"
}
```

**Response Variants**:

| Error                 | Description                      |
| --------------------- | -------------------------------- |
| `unauthorized`        | No authentication provided       |
| `invalid_token`       | Token is malformed or invalid    |
| `token_expired`       | Authentication token has expired |
| `invalid_credentials` | Wrong username/password          |

**Required Headers**:

For Consumer API:

```http theme={null}
X-Publishable-Key: pk_live_...
Authorization: Bearer <consumer_token>  (if authenticated)
```

For Admin API:

```http theme={null}
Authorization: Bearer <clerk_jwt>
```

**Resolution**:

```typescript theme={null}
// Handle 401 errors
if (response.status === 401) {
  const error = await response.json();

  if (error.error === "token_expired") {
    // Refresh the token
    await client.auth.refresh();
    // Retry the request
    return retry();
  }

  // Re-authenticate
  await client.auth.login();
}
```

## 403 Forbidden

Returned when the user is authenticated but lacks permission.

**Example Response**:

```json theme={null}
{
  "error": "forbidden",
  "message": "You don't have permission to access this resource"
}
```

**Common Causes**:

* Accessing another organization's resources
* Insufficient role/permissions
* Resource access restricted
* Session integrity validation failed

**Resolution**:

```typescript theme={null}
if (response.status === 403) {
  // Check if it's a device mismatch
  const error = await response.json();

  if (error.code === "FP002") {
    showMessage("Please use your original device to complete this action.");
  } else {
    showMessage("You don't have access to this resource.");
  }
}
```

## 404 Not Found

Returned when the requested resource doesn't exist.

**Example Response**:

```json theme={null}
{
  "error": "not_found",
  "message": "Experience not found"
}
```

**Common Causes**:

* Invalid resource ID
* Resource was deleted
* Resource not yet created
* Typo in resource path

**Resolution**:

```typescript theme={null}
if (response.status === 404) {
  // Redirect to a fallback page
  navigate("/experiences");
  showMessage("This experience is no longer available.");
}
```

## 409 Conflict

Returned when the request conflicts with current resource state.

**Example Response**:

```json theme={null}
{
  "error": "conflict",
  "message": "Already in queue"
}
```

**Common Scenarios**:

| Conflict                  | Description                              |
| ------------------------- | ---------------------------------------- |
| `already_in_queue`        | User already joined this queue           |
| `already_registered`      | User already registered for draw         |
| `already_exists`          | Resource with this ID already exists     |
| `concurrent_modification` | Resource was modified by another request |

**Resolution**:

```typescript theme={null}
if (response.status === 409) {
  const error = await response.json();

  if (error.error === "already_in_queue") {
    // Get existing queue status instead
    const status = await client.queues.getStatus(queueId);
    updateUI(status);
  }
}
```

## 422 Unprocessable Entity

Returned when request data fails validation.

**Example Response**:

```json theme={null}
{
  "error": "validation_error",
  "issues": [
    {
      "kind": "validation",
      "type": "email",
      "expected": "valid email",
      "received": "not-an-email",
      "message": "Invalid email format",
      "path": [{ "type": "object", "key": "email" }]
    },
    {
      "kind": "validation",
      "type": "min_length",
      "expected": "8 characters",
      "received": "5 characters",
      "message": "Password must be at least 8 characters",
      "path": [{ "type": "object", "key": "password" }]
    }
  ]
}
```

**Handling Validation Errors**:

```typescript theme={null}
if (response.status === 422) {
  const error = await response.json();

  if (error.error === "validation_error") {
    error.issues.forEach((issue) => {
      // Get the field path
      const field = issue.path[issue.path.length - 1]?.key || "root";

      // Display error on the form field
      setFieldError(field, issue.message);
    });
  }
}
```

**Common Validation Types**:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `required`   | Field is required                |
| `email`      | Must be valid email format       |
| `min_length` | Below minimum length             |
| `max_length` | Exceeds maximum length           |
| `pattern`    | Doesn't match expected pattern   |
| `unique`     | Value already exists (duplicate) |

## 429 Too Many Requests

Returned when rate limit is exceeded.

**Example Response**:

```json theme={null}
{
  "error": "rate_limited",
  "message": "Too many requests"
}
```

**Response Headers**:

```http theme={null}
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 30
```

**Rate Limit by Endpoint**:

| Endpoint Type  | Limit | Window     |
| -------------- | ----- | ---------- |
| Authentication | 10    | 1 minute   |
| Queue entry    | 5     | 10 seconds |
| General API    | 100   | 1 minute   |
| Webhook        | 1000  | 1 minute   |

**Resolution**:

```typescript theme={null}
if (response.status === 429) {
  const retryAfter = parseInt(response.headers.get("Retry-After") || "30");

  showMessage(`Please wait ${retryAfter} seconds before trying again.`);

  // Automatically retry after the delay
  await sleep(retryAfter * 1000);
  return retry();
}
```

## 500 Internal Server Error

Returned when an unexpected error occurs on the server.

**Example Response**:

```json theme={null}
{
  "error": "internal_error",
  "message": "An unexpected error occurred",
  "requestId": "req_abc123xyz"
}
```

**Resolution**:

```typescript theme={null}
if (response.status === 500) {
  const error = await response.json();

  // Log for debugging
  console.error("Server error:", error.requestId);

  // Show user-friendly message
  showMessage("Something went wrong. Please try again.");

  // Retry with exponential backoff
  if (attempt < maxRetries) {
    await sleep(Math.pow(2, attempt) * 1000);
    return retry();
  }
}
```

**When contacting support**, provide the `requestId` from the error response.

## 503 Service Unavailable

Returned when the service is temporarily unavailable.

**Example Response**:

```json theme={null}
{
  "error": "service_unavailable",
  "message": "Service temporarily unavailable"
}
```

**Common Causes**:

* Planned maintenance
* Deployment in progress
* Capacity limits
* Upstream service issues

**Resolution**:

```typescript theme={null}
if (response.status === 503) {
  // Show maintenance message
  showMessage("Service is temporarily unavailable. Please try again shortly.");

  // Retry with backoff
  const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
  await sleep(retryAfter * 1000);
  return retry();
}
```

## Database Error Codes

When database constraints are violated, specific error codes are returned.

### Unique Constraint Violations

**Example Response**:

```json theme={null}
{
  "error": "validation_error",
  "issues": [
    {
      "kind": "validation",
      "type": "unique",
      "expected": "unique_value",
      "received": "duplicate_value",
      "message": "validation.unique",
      "path": [{ "type": "object", "key": "email" }]
    }
  ]
}
```

**Common Fields**:

* `email` - Email already registered
* `name` - Name already in use
* `sku` - Product SKU already exists

## Error Handling Best Practices

### Global Error Handler

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

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

    switch (response.status) {
      case 401:
        // Handle authentication
        await handleUnauthorized(error);
        throw new AuthError(error);

      case 422:
        // Handle validation
        throw new ValidationError(error.issues);

      case 429:
        // Handle rate limiting
        const retryAfter = response.headers.get("Retry-After");
        throw new RateLimitError(parseInt(retryAfter || "30"));

      default:
        throw new ApiError(error);
    }
  }

  return response.json();
}
```

### Retry Logic

```typescript theme={null}
async function withRetry<T>(
  fn: () => Promise<T>,
  options: { maxRetries?: number; baseDelay?: number } = {}
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000 } = options;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      // Don't retry client errors (4xx) except rate limits
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error;
      }

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

      // Exponential backoff with jitter
      const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);
      await sleep(Math.min(delay, 30000));
    }
  }

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

## Related Resources

* [SDK Errors](/resources/troubleshooting/sdk-errors) - SDK error reference
* [Common Issues](/resources/troubleshooting/common-issues) - Troubleshooting guide
* [Contact Support](/resources/support/contact) - Get help
