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

# Error Handling

> Handle API errors gracefully in your integration

The meetergo API uses standard HTTP status codes and returns structured error responses. This guide covers how to handle errors effectively.

## Error Response Format

All errors follow this structure:

```json theme={null}
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ]
}
```

| Field        | Type   | Description                           |
| ------------ | ------ | ------------------------------------- |
| `statusCode` | number | HTTP status code                      |
| `message`    | string | Human-readable error message          |
| `error`      | string | Error type name                       |
| `errors`     | array  | Detailed validation errors (optional) |

## HTTP Status Codes

### Success Codes

| Code  | Meaning    | When Used                           |
| ----- | ---------- | ----------------------------------- |
| `200` | OK         | Successful GET, PATCH, PUT          |
| `201` | Created    | Successful POST creating a resource |
| `204` | No Content | Successful DELETE                   |

### Client Error Codes

| Code  | Meaning           | Common Causes                      |
| ----- | ----------------- | ---------------------------------- |
| `400` | Bad Request       | Invalid input, validation failed   |
| `401` | Unauthorized      | Missing or invalid API key         |
| `403` | Forbidden         | Insufficient permissions           |
| `404` | Not Found         | Resource doesn't exist             |
| `406` | Not Acceptable    | Limit reached (e.g., max webhooks) |
| `429` | Too Many Requests | Rate limit exceeded                |

### Server Error Codes

| Code  | Meaning               | Action                                            |
| ----- | --------------------- | ------------------------------------------------- |
| `500` | Internal Server Error | Retry with backoff, contact support if persistent |
| `502` | Bad Gateway           | Retry with backoff                                |
| `503` | Service Unavailable   | Retry with backoff                                |

## Common Errors and Solutions

### Authentication Errors

#### Missing Authorization Header

```json theme={null}
{
  "statusCode": 401,
  "message": "Missing authorization header",
  "error": "Unauthorized"
}
```

**Solution:** Include the `Authorization` header:

```bash theme={null}
-H "Authorization: Bearer YOUR_API_KEY"
```

#### Invalid API Key

```json theme={null}
{
  "statusCode": 401,
  "message": "Invalid API key",
  "error": "Unauthorized"
}
```

**Solution:** Verify your API key is correct and complete.

#### Expired API Key

```json theme={null}
{
  "statusCode": 401,
  "message": "API key has expired",
  "error": "Unauthorized"
}
```

**Solution:** Create a new API key in the dashboard.

#### Missing User Header

```json theme={null}
{
  "statusCode": 400,
  "message": "Missing required header 'x-meetergo-api-user-id' for API key authentication",
  "error": "Bad Request"
}
```

**Solution:** Include the user context header:

```bash theme={null}
-H "x-meetergo-api-user-id: USER_UUID"
```

### Booking Errors

#### Time Slot Unavailable

```json theme={null}
{
  "statusCode": 400,
  "message": "The selected time slot is no longer available",
  "error": "Bad Request"
}
```

**Solution:** Query fresh availability and select another slot.

```javascript theme={null}
async function bookWithRetry(bookingData, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await createBooking(bookingData);
    } catch (error) {
      if (error.statusCode === 400 &&
          error.message.includes('no longer available')) {
        // Refresh availability
        const windows = await getBookableWindows(bookingData.hostIds[0]);
        if (windows.length === 0) {
          throw new Error('No available slots');
        }
        // Try next available slot
        bookingData.start = windows[0];
      } else {
        throw error;
      }
    }
  }
  throw new Error('Could not book after retries');
}
```

#### Invalid Duration

```json theme={null}
{
  "statusCode": 400,
  "message": "Duration must match one of the allowed durations for this meeting type",
  "error": "Bad Request"
}
```

**Solution:** Check the meeting type's allowed durations before booking.

#### Missing Host

```json theme={null}
{
  "statusCode": 400,
  "message": "hostIds or queueId is required",
  "error": "Bad Request"
}
```

**Solution:** Provide either `hostIds` array or `queueId` for round-robin.

### Validation Errors

```json theme={null}
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "timezone", "message": "Invalid timezone identifier" }
  ]
}
```

**Solution:** Check the `errors` array for specific field issues.

```javascript theme={null}
function handleValidationErrors(error) {
  if (error.errors && Array.isArray(error.errors)) {
    const fieldErrors = {};
    for (const err of error.errors) {
      fieldErrors[err.field] = err.message;
    }
    return fieldErrors;
  }
  return { _general: error.message };
}
```

## Error Handling Patterns

### JavaScript/TypeScript

```typescript theme={null}
interface ApiError {
  statusCode: number;
  message: string;
  error: string;
  errors?: Array<{ field: string; message: string }>;
}

async function callApi<T>(
  url: string,
  options: RequestInit
): Promise<T> {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error: ApiError = await response.json();
    throw error;
  }

  return response.json();
}

// Usage with error handling
async function createUser(userData: UserData) {
  try {
    const user = await callApi('/v4/user', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
    return user;
  } catch (error) {
    if (error.statusCode === 401) {
      // Handle authentication error
      console.error('Authentication failed:', error.message);
      throw new Error('Please check your API key');
    }
    if (error.statusCode === 400) {
      // Handle validation error
      console.error('Validation failed:', error.errors);
      throw new Error(`Invalid input: ${error.message}`);
    }
    if (error.statusCode >= 500) {
      // Handle server error
      console.error('Server error:', error.message);
      throw new Error('Service temporarily unavailable');
    }
    throw error;
  }
}
```

### Python

```python theme={null}
import requests
from typing import Optional, Dict, Any

class MeetergoApiError(Exception):
    def __init__(self, status_code: int, message: str, errors: Optional[list] = None):
        self.status_code = status_code
        self.message = message
        self.errors = errors or []
        super().__init__(message)

def call_api(method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
    url = f"https://api.meetergo.com{endpoint}"
    headers = {
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    }

    response = requests.request(method, url, headers=headers, json=data)

    if not response.ok:
        error_data = response.json()
        raise MeetergoApiError(
            status_code=error_data.get('statusCode', response.status_code),
            message=error_data.get('message', 'Unknown error'),
            errors=error_data.get('errors')
        )

    return response.json()

# Usage with error handling
def create_booking(booking_data: dict) -> dict:
    try:
        return call_api('POST', '/v4/booking', booking_data)
    except MeetergoApiError as e:
        if e.status_code == 400 and 'no longer available' in e.message:
            # Handle slot taken
            raise ValueError('Time slot is no longer available')
        if e.status_code == 401:
            # Handle auth error
            raise PermissionError('Invalid API key')
        raise
```

## Retry Strategy

For transient errors (429, 5xx), implement exponential backoff:

```javascript theme={null}
async function withRetry(fn, maxRetries = 3) {
  let lastError;

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

      // Only retry on transient errors
      if (error.statusCode === 429 ||
          (error.statusCode >= 500 && error.statusCode < 600)) {

        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
        await sleep(delay);
      } else {
        // Don't retry client errors (4xx except 429)
        throw error;
      }
    }
  }

  throw lastError;
}

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Usage
const booking = await withRetry(() => createBooking(data));
```

## Best Practices

<Check>
  **Always check status codes** - Don't assume success
</Check>

<Check>
  **Log errors with context** - Include request details for debugging
</Check>

<Check>
  **Use exponential backoff** - For rate limits and server errors
</Check>

<Check>
  **Parse validation errors** - Show specific field errors to users
</Check>

<Check>
  **Handle race conditions** - Slots can be booked between availability query and booking
</Check>

<Warning>
  **Don't retry 4xx errors** (except 429) - They indicate client issues that won't resolve with retries
</Warning>
