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

# Error handling

> Understanding and handling API errors

## Overview

The Voyant API uses standard HTTP status codes to indicate success or failure of requests. Error responses include a JSON body with details about what went wrong.

## Error response format

All errors return a JSON object with an `error` field:

```json theme={null}
{
  "error": "Error message description"
}
```

Some errors may include additional fields:

```json theme={null}
{
  "error": "Validation failed",
  "details": {
    "field": "email",
    "message": "Invalid email format"
  }
}
```

## HTTP status codes

### 2xx Success

| Code | Status     | Description                             |
| ---- | ---------- | --------------------------------------- |
| 200  | OK         | Request succeeded                       |
| 201  | Created    | Resource created successfully           |
| 204  | No Content | Request succeeded with no response body |

### 4xx Client errors

| Code | Status               | Description                              |
| ---- | -------------------- | ---------------------------------------- |
| 400  | Bad Request          | Invalid request format or parameters     |
| 401  | Unauthorized         | Missing or invalid Authorization token   |
| 403  | Forbidden            | Valid key but insufficient permissions   |
| 404  | Not Found            | Resource not found                       |
| 409  | Conflict             | Request conflicts with existing resource |
| 422  | Unprocessable Entity | Valid format but invalid data            |
| 429  | Too Many Requests    | Rate limit exceeded                      |

### 5xx Server errors

| Code | Status                | Description              |
| ---- | --------------------- | ------------------------ |
| 500  | Internal Server Error | Unexpected server error  |
| 502  | Bad Gateway           | Upstream service error   |
| 503  | Service Unavailable   | Temporary service outage |
| 504  | Gateway Timeout       | Request timeout          |

## Common errors

### 400 Bad Request

Invalid request format, missing required fields, or malformed JSON.

<ResponseExample>
  ```json theme={null}
  {
    "error": "Invalid request"
  }
  ```
</ResponseExample>

**Common causes:**

* Malformed JSON in request body
* Missing required fields
* Invalid parameter types
* Invalid UUID format

**How to fix:**

* Validate JSON before sending
* Check required fields in documentation
* Ensure correct data types
* Use proper UUID v4 format

### 401 Unauthorized

Missing or invalid Authorization token.

<ResponseExample>
  ```json theme={null}
  {
    "error": "Unauthorized"
  }
  ```
</ResponseExample>

**Common causes:**

* Missing `Authorization: Bearer` header
* Invalid API key
* Revoked API key
* Extra spaces in API key

**How to fix:**

* Verify Authorization header is included in request
* Check for typos or extra characters
* Generate new API key if needed
* Ensure key hasn't been revoked

### 404 Not Found

Requested resource doesn't exist or you don't have access.

<ResponseExample>
  ```json theme={null}
  {
    "error": "Not found"
  }
  ```
</ResponseExample>

**Common causes:**

* Invalid resource ID
* Resource deleted
* No access to resource (wrong workspace)
* Typo in endpoint URL

**How to fix:**

* Verify resource ID is correct
* Check resource still exists
* Confirm resource belongs to your workspace
* Review endpoint URL for typos

### 429 Rate Limit Exceeded

Too many requests in a short time period.

<ResponseExample>
  ```json theme={null}
  {
    "error": "Rate limit exceeded",
    "retry_after": 30
  }
  ```
</ResponseExample>

**Common causes:**

* Exceeding 3,000 requests per minute
* Surpassing the 30 requests per second burst window
* Missing rate limit handling

**How to fix:**

* Implement exponential backoff
* Use `retry_after` value for delays
* Cache responses when possible
* Coordinate with support ahead of high-volume events

### 500 Internal Server Error

Unexpected server error.

<ResponseExample>
  ```json theme={null}
  {
    "error": "Internal server error"
  }
  ```
</ResponseExample>

**Common causes:**

* Temporary service issue
* Database connection problem
* Upstream service failure

**How to fix:**

* Retry request with exponential backoff
* Contact support if persistent
* Include `X-Request-ID` when reporting

## Error handling best practices

### 1. Check status codes

Always check the HTTP status code before parsing the response:

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://api.voyantcloud.com/v1/products', {
    headers: { Authorization: `Bearer ${process.env.VOYANT_API_KEY}` }
  });

  if (!response.ok) {
  const error = await response.json();
  throw new Error(`API error (${response.status}): ${error.error}`);
  }

  const data = await response.json();

  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
    'https://api.voyantcloud.com/v1/products',
    headers={'Authorization': f"Bearer {os.environ['VOYANT_API_KEY']}"}
  )

  if not response.ok:
      error = response.json()
      raise Exception(f"API error ({response.status_code}): {error['error']}")

  data = response.json()
  ```
</CodeGroup>

### 2. Implement retry logic

Use exponential backoff for retrying failed requests:

<CodeGroup>
  ```javascript Node.js theme={null}
  async function fetchWithRetry(url, options, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        const response = await fetch(url, options);
        
        if (response.status === 429) {
          const retryAfter = response.headers.get('retry-after') || (2 ** i);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }
        
        if (response.status >= 500) {
          await new Promise(resolve => setTimeout(resolve, (2 ** i) * 1000));
          continue;
        }
        
        return response;
      } catch (error) {
        if (i === maxRetries - 1) throw error;
        await new Promise(resolve => setTimeout(resolve, (2 ** i) * 1000));
      }
    }
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  def fetch_with_retry(url, headers, max_retries=3):
      for i in range(max_retries):
          try:
              response = requests.get(url, headers=headers)

              if response.status_code == 429:
                  retry_after = int(response.headers.get('retry-after', 2 ** i))
                  time.sleep(retry_after)
                  continue

              if response.status_code >= 500:
                  time.sleep(2 ** i)
                  continue

              return response
          except Exception as e:
              if i == max_retries - 1:
                  raise e
              time.sleep(2 ** i)
  ```
</CodeGroup>

### 3. Log request IDs

Always log the `X-Request-ID` header for debugging:

```javascript theme={null}
const requestId = response.headers.get("x-request-id")
console.error(`API error: ${error.error}`, { requestId })
```

<Tip>Include the request ID when contacting support for faster troubleshooting.</Tip>

### 4. Handle rate limits

Respect rate limit headers to avoid throttling:

```javascript theme={null}
const limit = response.headers.get("x-ratelimit-limit")
const remaining = response.headers.get("x-ratelimit-remaining")
const reset = response.headers.get("x-ratelimit-reset")

if (remaining < 10) {
  console.warn(`Rate limit warning: ${remaining}/${limit} remaining`)
}
```

### 5. Validate inputs

Validate data before sending to avoid 400 errors:

```javascript theme={null}
function validatePriceRequest(data) {
  if (!data.productId || !isUUID(data.productId)) {
    throw new Error("Invalid productId")
  }

  if (!data.departureId || !isUUID(data.departureId)) {
    throw new Error("Invalid departureId")
  }

  if (!data.pax || data.pax.adults < 1) {
    throw new Error("At least one adult required")
  }

  return data
}

const validData = validatePriceRequest(requestData)
```

## Error handling patterns

### Graceful degradation

```javascript theme={null}
async function getProducts() {
  try {
    const response = await fetch("https://api.voyantcloud.com/v1/products", {
      headers: { Authorization: `Bearer ${process.env.VOYANT_API_KEY}` },
    })

    if (!response.ok) {
      console.error("Failed to fetch products:", await response.json())
      return [] // Return empty array instead of throwing
    }

    return await response.json()
  } catch (error) {
    console.error("Network error:", error)
    return [] // Fallback to empty array
  }
}
```

### User-friendly messages

Map technical errors to user-friendly messages:

```javascript theme={null}
function getUserMessage(statusCode, error) {
  const messages = {
    400: "Invalid request. Please check your input.",
    401: "Authentication failed. Please check your Authorization token.",
    404: "Resource not found. It may have been deleted.",
    429: "Too many requests. Please try again in a moment.",
    500: "Server error. Please try again later.",
    503: "Service temporarily unavailable. Please try again.",
  }

  return messages[statusCode] || "An unexpected error occurred."
}
```

### Circuit breaker pattern

Prevent cascading failures with circuit breaker:

```javascript theme={null}
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0
    this.threshold = threshold
    this.timeout = timeout
    this.state = "CLOSED" // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now()
  }

  async call(fn) {
    if (this.state === "OPEN") {
      if (Date.now() < this.nextAttempt) {
        throw new Error("Circuit breaker is OPEN")
      }
      this.state = "HALF_OPEN"
    }

    try {
      const result = await fn()
      this.onSuccess()
      return result
    } catch (error) {
      this.onFailure()
      throw error
    }
  }

  onSuccess() {
    this.failureCount = 0
    this.state = "CLOSED"
  }

  onFailure() {
    this.failureCount++
    if (this.failureCount >= this.threshold) {
      this.state = "OPEN"
      this.nextAttempt = Date.now() + this.timeout
    }
  }
}
```

## Debugging errors

### Enable verbose logging

Add detailed logging for development:

```javascript theme={null}
const DEBUG = process.env.NODE_ENV === "development"

async function apiRequest(endpoint, options) {
  if (DEBUG) {
    console.log("Request:", { endpoint, options })
  }

  const response = await fetch(`https://api.voyantcloud.com${endpoint}`, options)

  if (DEBUG) {
    console.log("Response:", {
      status: response.status,
      headers: Object.fromEntries(response.headers),
      requestId: response.headers.get("x-request-id"),
    })
  }

  return response
}
```

### Check API status

Before debugging, verify API status:

```bash theme={null}
curl -I https://api.voyantcloud.com/v1/products
```

### Test authentication

Isolate authentication issues:

```bash theme={null}
curl https://api.voyantcloud.com/v1/products \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -v
```

## Getting help

If you encounter persistent errors:

<Steps>
  <Step title="Review documentation">
    Verify your implementation matches the [API reference](/api-reference/introduction)
  </Step>

  <Step title="Collect information">
    * Request ID (`X-Request-ID`) from response headers
    * Endpoint, method, and sanitized request payload
    * Response status code and body
    * Workspace ID and timestamp of the failure
  </Step>

  <Step title="Contact support">
    Email [help@voyantcloud.com](mailto:help@voyantcloud.com) with the details above or open a ticket from the dashboard.
  </Step>
</Steps>
