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

# Error Handling

> Error codes, validation errors, structured error responses, and the CommonErrors helper system.

## Overview

The High IQ API uses structured error responses with machine-readable codes and human-readable messages. Every error follows the same envelope format, making client-side error handling consistent and predictable.

## Error Response Format

All errors return `success: false` with a structured `error` object:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Strain not found: purple-unicorn",
    "details": {
      "resource": "Strain",
      "id": "purple-unicorn"
    }
  },
  "meta": {
    "timestamp": "2026-02-16T12:00:00.000Z",
    "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "path": "/api/v1/strains/slug/purple-unicorn",
    "method": "GET"
  }
}
```

### Error Fields

| Field            | Type      | Description                                                    |
| ---------------- | --------- | -------------------------------------------------------------- |
| `error.code`     | `string`  | Machine-readable error code (e.g., `NOT_FOUND`, `BAD_REQUEST`) |
| `error.message`  | `string`  | Human-readable description of the error                        |
| `error.details`  | `unknown` | Optional additional context (varies by error type)             |
| `error.stack`    | `string`  | Stack trace (non-production environments only)                 |
| `meta.timestamp` | `string`  | ISO 8601 timestamp of when the error occurred                  |
| `meta.requestId` | `string`  | Unique request identifier for debugging                        |
| `meta.path`      | `string`  | The request path that triggered the error                      |
| `meta.method`    | `string`  | The HTTP method used                                           |

<Note>
  The `error.stack` field is **only included in non-production environments**. In production, stack traces are stripped to avoid leaking internal implementation details.
</Note>

## Error Codes

### Client Errors (4xx)

| Code                       | HTTP Status | Description                              |
| -------------------------- | ----------- | ---------------------------------------- |
| `BAD_REQUEST`              | 400         | Invalid request parameters or body       |
| `VALIDATION_ERROR`         | 400         | Request body failed schema validation    |
| `UNAUTHORIZED`             | 401         | Authentication required but not provided |
| `INVALID_TOKEN`            | 401         | JWT signature verification failed        |
| `TOKEN_EXPIRED`            | 401         | JWT has expired                          |
| `FORBIDDEN`                | 403         | Valid auth but insufficient permissions  |
| `INSUFFICIENT_PERMISSIONS` | 403         | User lacks required role or permission   |
| `NOT_FOUND`                | 404         | Requested resource does not exist        |
| `RATE_LIMIT_EXCEEDED`      | 429         | Too many requests in the time window     |

### Resource-Specific Errors (4xx)

| Code                 | HTTP Status | Description                                     |
| -------------------- | ----------- | ----------------------------------------------- |
| `STRAIN_NOT_FOUND`   | 404         | Strain with the given slug or ID does not exist |
| `ORDER_NOT_FOUND`    | 404         | Order with the given ID does not exist          |
| `NOTEBOOK_NOT_FOUND` | 404         | Notebook with the given ID does not exist       |
| `USER_NOT_FOUND`     | 404         | User with the given ID does not exist           |

### Server Errors (5xx)

| Code                     | HTTP Status | Description                                   |
| ------------------------ | ----------- | --------------------------------------------- |
| `INTERNAL_ERROR`         | 500         | Unexpected server error                       |
| `DATABASE_ERROR`         | 500         | Database operation failed                     |
| `EXTERNAL_SERVICE_ERROR` | 500         | Third-party service (AI, external API) failed |

## Common Error Examples

### Not Found

```bash theme={null}
curl "https://api.thisiswhyimhigh.com/api/v1/strains/slug/nonexistent-strain"
```

```json theme={null}
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Strain not found: nonexistent-strain",
    "details": {
      "resource": "Strain",
      "id": "nonexistent-strain"
    }
  }
}
```

### Validation Error

When a request body fails Zod schema validation, the API returns a `VALIDATION_ERROR` with the full list of validation issues in the `details` field.

```bash theme={null}
curl -X POST "https://api.thisiswhyimhigh.com/api/v1/notebooks/stream/batch" \
  -H "Content-Type: application/json" \
  -d '{"sectionKeys": []}'
```

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "code": "too_small",
        "minimum": 1,
        "type": "array",
        "inclusive": true,
        "exact": false,
        "message": "At least one section key is required",
        "path": ["sectionKeys"]
      }
    ]
  }
}
```

### Bad Request

```json theme={null}
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Invalid section key",
    "details": {
      "invalidKeys": ["not_a_real_section"],
      "validKeys": ["overview", "terpene_analysis", "effects_breakdown"]
    }
  }
}
```

### Rate Limit Exceeded

```json theme={null}
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded",
    "details": {
      "retryAfter": 45
    }
  }
}
```

### Database Error

In non-production environments, database errors include the underlying error for debugging. In production, they return a generic message.

<Tabs>
  <Tab title="Production">
    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "DATABASE_ERROR",
        "message": "Database operation failed"
      }
    }
    ```
  </Tab>

  <Tab title="Development">
    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "DATABASE_ERROR",
        "message": "Database operation failed",
        "details": {
          "message": "relation \"strains_v2\" does not exist",
          "code": "42P01"
        }
      }
    }
    ```
  </Tab>
</Tabs>

## CommonErrors Helper

The API uses a `CommonErrors` helper object to generate consistent error responses throughout the codebase. These are the pre-built error factories:

| Helper                                           | HTTP Status | Code                  | Default Message                  |
| ------------------------------------------------ | ----------- | --------------------- | -------------------------------- |
| `CommonErrors.notFound(c, resource, id?)`        | 404         | `NOT_FOUND`           | `"{resource} not found: {id}"`   |
| `CommonErrors.unauthorized(c, message?)`         | 401         | `UNAUTHORIZED`        | `"Authentication required"`      |
| `CommonErrors.forbidden(c, message?)`            | 403         | `FORBIDDEN`           | `"Insufficient permissions"`     |
| `CommonErrors.badRequest(c, message, details?)`  | 400         | `BAD_REQUEST`         | Custom message                   |
| `CommonErrors.validationError(c, errors)`        | 400         | `VALIDATION_ERROR`    | `"Validation failed"`            |
| `CommonErrors.databaseError(c, error)`           | 500         | `DATABASE_ERROR`      | `"Database operation failed"`    |
| `CommonErrors.internalError(c, error?)`          | 500         | `INTERNAL_ERROR`      | `"An unexpected error occurred"` |
| `CommonErrors.rateLimitExceeded(c, retryAfter?)` | 429         | `RATE_LIMIT_EXCEEDED` | `"Rate limit exceeded"`          |

## Custom ApiError Class

For route handlers that need to throw errors, the API provides a custom `ApiError` class:

```typescript theme={null}
import { ApiError } from '../lib/api-response.js';

// Throw a structured error
throw new ApiError(
  404,                    // HTTP status code
  'STRAIN_NOT_FOUND',    // Error code
  'Strain not found',    // Human-readable message
  { slug: 'bad-strain' } // Optional details
);
```

The global error handler catches `ApiError` instances and formats them into the standard error response envelope.

## Client-Side Handling

### TypeScript Type Guard

Use the provided type guards to safely check response types:

```typescript theme={null}
import type { ApiResponse, ApiSuccessResponse, ApiErrorResponse } from '@tiwih/types';

function isErrorResponse(response: ApiResponse): response is ApiErrorResponse {
  return !response.success;
}

function isSuccessResponse<T>(response: ApiResponse<T>): response is ApiSuccessResponse<T> {
  return response.success;
}

// Usage
const response = await fetch('/api/v1/strains/slug/blue-dream');
const data: ApiResponse<Strain> = await response.json();

if (isErrorResponse(data)) {
  console.error(`Error ${data.error.code}: ${data.error.message}`);
} else {
  console.log(data.data); // Typed as Strain
}
```

### HTTP Status Code Handling

Always check the HTTP status code first, then parse the response body for details:

```typescript theme={null}
const response = await fetch(url);

switch (response.status) {
  case 200:
    return (await response.json()).data;
  case 400:
    throw new Error('Invalid request');
  case 401:
    // Redirect to login or refresh token
    break;
  case 404:
    return null;
  case 429:
    // Respect Retry-After header
    const retryAfter = response.headers.get('Retry-After');
    break;
  case 500:
    throw new Error('Server error');
}
```
