GraphQL Error Format
SmartMenu API returns errors in the standard GraphQL format:{
"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
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
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
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
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
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
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
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
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
Always Handle Errors
Never let errors bubble up to crash your app. Always catch and handle.
User-Friendly Messages
Don’t show technical error messages to users. Translate to helpful guidance.
Fail Gracefully
If nutrition isn’t available, the dish should still be orderable.
Log for Debugging
Log errors with context for debugging, but don’t expose details to users.

