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

# Error Codes

> EngageFabric API error codes and troubleshooting guide. Handle validation errors, rate limits, authentication failures, and tenant isolation errors.

All EngageFabric API errors follow a consistent format:

```json theme={null}
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": [...],
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Authentication Errors

| Code                | HTTP Status | Description                                    |
| ------------------- | ----------- | ---------------------------------------------- |
| `UNAUTHORIZED`      | 401         | Missing or invalid API key/token               |
| `TOKEN_EXPIRED`     | 401         | JWT token has expired                          |
| `INVALID_API_KEY`   | 401         | API key is malformed or revoked                |
| `FORBIDDEN`         | 403         | Valid credentials but insufficient permissions |
| `PROJECT_SUSPENDED` | 403         | Project has been suspended                     |

### Example

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired API key",
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Validation Errors

| Code                | HTTP Status | Description                    |
| ------------------- | ----------- | ------------------------------ |
| `VALIDATION_ERROR`  | 400         | Request body validation failed |
| `INVALID_PARAMETER` | 400         | Query parameter is invalid     |
| `MISSING_FIELD`     | 400         | Required field is missing      |
| `INVALID_FORMAT`    | 400         | Field format is incorrect      |

### Example

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "externalUserId",
        "message": "Must be a non-empty string"
      },
      {
        "field": "email",
        "message": "Must be a valid email address"
      }
    ],
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Resource Errors

| Code                    | HTTP Status | Description                            |
| ----------------------- | ----------- | -------------------------------------- |
| `NOT_FOUND`             | 404         | Requested resource does not exist      |
| `PLAYER_NOT_FOUND`      | 404         | Player with given ID not found         |
| `QUEST_NOT_FOUND`       | 404         | Quest with given ID not found          |
| `LEADERBOARD_NOT_FOUND` | 404         | Leaderboard with given ID not found    |
| `LOBBY_NOT_FOUND`       | 404         | Lobby with given ID not found          |
| `ALREADY_EXISTS`        | 409         | Resource already exists                |
| `CONFLICT`              | 409         | Operation conflicts with current state |

### Example

```json theme={null}
{
  "error": {
    "code": "PLAYER_NOT_FOUND",
    "message": "Player with ID 'player-123' not found",
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Rate Limiting Errors

| Code             | HTTP Status | Description            |
| ---------------- | ----------- | ---------------------- |
| `RATE_LIMITED`   | 429         | Too many requests      |
| `QUOTA_EXCEEDED` | 429         | Monthly quota exceeded |

### Response Headers

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699900000
Retry-After: 60
```

### Example

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 60 seconds.",
    "details": {
      "limit": 100,
      "window": "1 minute",
      "retryAfter": 60
    },
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Business Logic Errors

| Code                      | HTTP Status | Description                       |
| ------------------------- | ----------- | --------------------------------- |
| `INSUFFICIENT_CURRENCY`   | 400         | Not enough currency for operation |
| `INSUFFICIENT_LIVES`      | 400         | No lives remaining                |
| `QUEST_NOT_AVAILABLE`     | 400         | Quest is locked or expired        |
| `QUEST_ALREADY_COMPLETED` | 400         | Quest already finished            |
| `LOBBY_FULL`              | 400         | Lobby has reached max players     |
| `LOBBY_STARTED`           | 400         | Cannot join started lobby         |
| `PLAYER_BANNED`           | 403         | Player is banned from action      |
| `PLAYER_MUTED`            | 403         | Player cannot send messages       |

### Example

```json theme={null}
{
  "error": {
    "code": "INSUFFICIENT_CURRENCY",
    "message": "Not enough coins. Required: 100, Available: 50",
    "details": {
      "currencyId": "coins",
      "required": 100,
      "available": 50
    },
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

***

## Server Errors

| Code                  | HTTP Status | Description                     |
| --------------------- | ----------- | ------------------------------- |
| `INTERNAL_ERROR`      | 500         | Unexpected server error         |
| `SERVICE_UNAVAILABLE` | 503         | Service temporarily unavailable |
| `TIMEOUT`             | 504         | Request timed out               |

### Example

```json theme={null}
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred. Please try again.",
    "requestId": "req-abc123",
    "timestamp": "2025-01-21T10:00:00Z"
  }
}
```

<Warning>
  If you receive a 500 error, please contact support with the `requestId`
  for investigation.
</Warning>

***

## Handling Errors

### JavaScript Example

```javascript theme={null}
try {
  const player = await client.players.get('player-123');
} catch (error) {
  switch (error.code) {
    case 'PLAYER_NOT_FOUND':
      // Create the player
      await client.players.identify({ externalUserId: 'player-123' });
      break;
    case 'RATE_LIMITED':
      // Wait and retry
      await sleep(error.details.retryAfter * 1000);
      return retry();
    case 'UNAUTHORIZED':
      // Refresh credentials
      await refreshApiKey();
      break;
    default:
      console.error('Unexpected error:', error);
  }
}
```

### Retry Strategy

For transient errors (429, 503, 504), implement exponential backoff:

```javascript theme={null}
async function withRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      if (!isRetryable(error)) throw error;

      const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
      await sleep(delay);
    }
  }
}

function isRetryable(error) {
  return ['RATE_LIMITED', 'SERVICE_UNAVAILABLE', 'TIMEOUT'].includes(error.code);
}
```
