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

# Error Handling

> Handle SmartMenu API errors gracefully

This guide covers how to handle SmartMenu API errors gracefully, ensuring a good user experience even when things go wrong.

## GraphQL Error Format

SmartMenu API returns errors in the standard GraphQL format:

```json theme={null}
{
  "data": null,
  "errors": [
    {
      "message": "Missing required header: X-Session-ID",
      "extensions": {
        "code": "MISSING_HEADER",
        "field": "X-Session-ID"
      },
      "path": ["search"]
    }
  ]
}
```

## Error Codes

### Authentication Errors

| Code             | HTTP Status | Description                         | User Action                 |
| ---------------- | ----------- | ----------------------------------- | --------------------------- |
| `MISSING_HEADER` | 400         | Required header not provided        | Fix request headers         |
| `INVALID_TOKEN`  | 401         | API key invalid or expired          | Check API key configuration |
| `FORBIDDEN`      | 403         | Key lacks access to requested chain | Contact EveryBite           |

### Validation Errors

| Code              | HTTP Status | Description                 | User Action          |
| ----------------- | ----------- | --------------------------- | -------------------- |
| `INVALID_INPUT`   | 400         | Request parameter malformed | Check request format |
| `CHAIN_NOT_FOUND` | 404         | Chain ID doesn't exist      | Verify chain ID      |
| `DISH_NOT_FOUND`  | 404         | Dish ID doesn't exist       | Handle gracefully    |

### Rate Limiting

| Code           | HTTP Status | Description       | User Action    |
| -------------- | ----------- | ----------------- | -------------- |
| `RATE_LIMITED` | 429         | Too many requests | Wait and retry |

### Server Errors

| Code                  | HTTP Status | Description              | User Action                |
| --------------------- | ----------- | ------------------------ | -------------------------- |
| `INTERNAL_ERROR`      | 500         | Server error             | Retry or graceful fallback |
| `SERVICE_UNAVAILABLE` | 503         | Service temporarily down | Retry with backoff         |

## Handling Errors in Code

### Basic Error Handler

```typescript theme={null}
async function smartMenuQuery(query, variables) {
  try {
    const response = await fetch(ENDPOINT, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': API_KEY,
        'X-Session-ID': sessionId,
      },
      body: JSON.stringify({ query, variables })
    });

    // Handle HTTP errors
    if (!response.ok) {
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 5;
        throw new RateLimitError(retryAfter);
      }
      throw new ApiError(response.status, 'API request failed');
    }

    const { data, errors } = await response.json();

    // Handle GraphQL errors
    if (errors && errors.length > 0) {
      const error = errors[0];
      throw new GraphQLError(
        error.message,
        error.extensions?.code
      );
    }

    return data;
  } catch (error) {
    handleError(error);
    throw error;
  }
}
```

### Error Classes

```typescript theme={null}
class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

class GraphQLError extends Error {
  constructor(message: string, public code?: string) {
    super(message);
    this.name = 'GraphQLError';
  }
}

class RateLimitError extends Error {
  constructor(public retryAfter: number) {
    super(`Rate limited. Retry after ${retryAfter} seconds.`);
    this.name = 'RateLimitError';
  }
}
```

### Centralized Error Handler

```typescript theme={null}
function handleError(error: Error) {
  if (error instanceof RateLimitError) {
    // Show temporary message, auto-retry
    showToast(`Please wait ${error.retryAfter} seconds...`);
    return;
  }

  if (error instanceof GraphQLError) {
    switch (error.code) {
      case 'DISH_NOT_FOUND':
        showToast('This dish is no longer available');
        break;
      case 'CHAIN_NOT_FOUND':
        showToast('Restaurant not found');
        break;
      case 'INVALID_TOKEN':
        // This shouldn't happen in production
        console.error('Invalid API key');
        break;
      default:
        showToast('Something went wrong. Please try again.');
    }
    return;
  }

  // Network or unexpected errors
  showToast('Unable to load menu. Please check your connection.');
}
```

## Graceful Degradation

When SmartMenu API is unavailable, your app should continue to function.

### Fallback Pattern

```typescript theme={null}
async function getDishDetails(dishId) {
  try {
    // Try SmartMenu API
    const data = await smartMenuQuery(DISH_QUERY, { id: dishId });
    return {
      ...data.dish,
      nutritionAvailable: true
    };
  } catch (error) {
    console.error('SmartMenu API error:', error);

    // Fall back to your own data (without nutrition)
    const localDish = await getLocalDishData(dishId);
    return {
      ...localDish,
      nutritionAvailable: false,
      nutrition: null,
      allergens: [],
      diets: []
    };
  }
}
```

### UI Fallback

```jsx theme={null}
function DishDetails({ dish }) {
  if (!dish.nutritionAvailable) {
    return (
      <div className="dish-details">
        <h2>{dish.name}</h2>
        <p>{dish.description}</p>

        <div className="nutrition-unavailable">
          <InfoIcon />
          <span>Nutrition information temporarily unavailable</span>
        </div>
      </div>
    );
  }

  return (
    <div className="dish-details">
      <h2>{dish.name}</h2>
      <p>{dish.description}</p>
      <NutritionPanel nutrition={dish.nutrition} />
      <AllergenBadges allergens={dish.allergens} />
    </div>
  );
}
```

## Retry Logic

### Exponential Backoff

```typescript theme={null}
async function fetchWithRetry(
  fn: () => Promise<any>,
  maxRetries = 3,
  baseDelay = 1000
) {
  let lastError: Error;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Don't retry client errors (4xx)
      if (error instanceof ApiError && error.status < 500) {
        throw error;
      }

      // Don't retry on last attempt
      if (attempt === maxRetries - 1) {
        throw error;
      }

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

  throw lastError;
}

// Usage
const data = await fetchWithRetry(() =>
  smartMenuQuery(SEARCH_QUERY, variables)
);
```

### Rate Limit Handling

```typescript theme={null}
async function fetchWithRateLimitRetry(fn: () => Promise<any>) {
  try {
    return await fn();
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Wait for the specified time
      await sleep(error.retryAfter * 1000);
      // Retry once
      return await fn();
    }
    throw error;
  }
}
```

## User-Facing Error Messages

| Error Code        | User Message                                                |
| ----------------- | ----------------------------------------------------------- |
| `RATE_LIMITED`    | "Please wait a moment and try again"                        |
| `DISH_NOT_FOUND`  | "This dish is no longer available"                          |
| `CHAIN_NOT_FOUND` | "Restaurant not found"                                      |
| `INTERNAL_ERROR`  | "Something went wrong. Please try again."                   |
| Network Error     | "Unable to connect. Please check your internet connection." |

## Logging and Monitoring

```typescript theme={null}
function logError(error: Error, context: object) {
  const errorLog = {
    timestamp: new Date().toISOString(),
    name: error.name,
    message: error.message,
    code: error instanceof GraphQLError ? error.code : undefined,
    ...context
  };

  // Send to your logging service
  console.error('SmartMenu API Error:', errorLog);

  // Track in analytics
  analytics.track('smartmenu_error', {
    error_type: error.name,
    error_code: error instanceof GraphQLError ? error.code : 'unknown',
    ...context
  });
}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Always Handle Errors" icon="shield">
    Never let errors bubble up to crash your app. Always catch and handle.
  </Card>

  <Card title="User-Friendly Messages" icon="message">
    Don't show technical error messages to users. Translate to helpful guidance.
  </Card>

  <Card title="Fail Gracefully" icon="life-ring">
    If nutrition isn't available, the dish should still be orderable.
  </Card>

  <Card title="Log for Debugging" icon="bug">
    Log errors with context for debugging, but don't expose details to users.
  </Card>
</CardGroup>
