> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.etera.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.etera.dev/_mcp/server.

# Error Handling

## Error Response Format

All ETERA APIs return errors in a consistent JSON format:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The 'checkIn' field is required",
    "status": 400,
    "details": []
  }
}
```

---

## HTTP Status Codes

#### 4xx Client Errors

| Code  | Meaning                                                        |
| ----- | -------------------------------------------------------------- |
| `400` | **Bad Request** — Invalid parameters or malformed request body |
| `401` | **Unauthorized** — Missing or invalid API key                  |
| `403` | **Forbidden** — Insufficient permissions for this resource     |
| `404` | **Not Found** — Resource does not exist                        |
| `409` | **Conflict** — Resource state conflict (e.g., double booking)  |
| `422` | **Unprocessable Entity** — Validation failure on request body  |
| `429` | **Too Many Requests** — Rate limit exceeded                    |

#### 5xx Server Errors

| Code  | Meaning                                                     |
| ----- | ----------------------------------------------------------- |
| `500` | **Internal Server Error** — Something went wrong on our end |
| `502` | **Bad Gateway** — Upstream service unavailable              |
| `503` | **Service Unavailable** — Service temporarily down          |
| `504` | **Gateway Timeout** — Upstream service took too long        |

> **Warn**
>
> For 5xx errors, always implement retry logic with exponential backoff. These are typically transient issues.

---

## Error Codes

Common error codes returned in the `error.code` field:

| Code                   | Description                    |
| ---------------------- | ------------------------------ |
| `VALIDATION_ERROR`     | Request body failed validation |
| `AUTHENTICATION_ERROR` | Invalid or expired credentials |
| `AUTHORIZATION_ERROR`  | Insufficient permissions       |
| `NOT_FOUND`            | Requested resource not found   |
| `RATE_LIMIT_EXCEEDED`  | Too many requests              |
| `BOOKING_CONFLICT`     | Conflicting reservation        |
| `PAYMENT_FAILED`       | Payment processing error       |
| `SERVICE_UNAVAILABLE`  | Upstream service is down       |

---

## Retry Strategy

For `429` and `5xx` errors, implement exponential backoff:

#### First retry

Wait **1 second**, then retry the request

#### Second retry

Wait **2 seconds**, then retry

#### Third retry

Wait **4 seconds**, then retry

#### Give up

After **3 retries**, log the error and notify the user

### Implementation

**`TypeScript`**

```typescript title="TypeScript"
async function fetchWithRetry(
  url: string,
  options: RequestInit,
  retries = 3
) {
  for (let attempt = 0; attempt < retries; attempt++) {
    const response = await fetch(url, options);

    if (response.ok) return response;

    if (response.status === 429 || response.status >= 500) {
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise((resolve) => setTimeout(resolve, delay));
      continue;
    }

    throw new Error(`Request failed: ${response.status}`);
  }
  throw new Error("Max retries exceeded");
}
```

**`Python`**

```python title="Python"
import time
import requests

def fetch_with_retry(url, headers, retries=3):
    for attempt in range(retries):
        response = requests.get(url, headers=headers)

        if response.ok:
            return response

        if response.status_code in (429, 500, 502, 503, 504):
            delay = (2 ** attempt)
            time.sleep(delay)
            continue

        response.raise_for_status()

    raise Exception("Max retries exceeded")
```

> **Info**
>
> For `429` responses, check the `Retry-After` header if present — it indicates exactly how long to wait before retrying.

---

## Common Scenarios

#### Authentication errors (401)

Your token has expired or is invalid. Re-authenticate using your preferred method:

```typescript
if (response.status === 401) {
  const newToken = await refreshAuth();
  // Retry with new token
}
```

See [Authentication](/guides/authentication) for details on token refresh.

#### Validation errors (400/422)

Check the `error.details` array for field-level validation messages:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "status": 422,
    "details": [
      { "field": "checkIn", "message": "Must be a future date" },
      { "field": "partySize", "message": "Must be between 1 and 20" }
    ]
  }
}
```

#### Rate limiting (429)

You've exceeded the rate limit. Check these headers:

| Header                  | Description                           |
| ----------------------- | ------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests per window           |
| `X-RateLimit-Remaining` | Remaining requests in current window  |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets |
| `Retry-After`           | Seconds to wait before retrying       |

#### Booking conflicts (409)

The requested time slot or resource is no longer available. Fetch updated availability and let the user choose an alternative.

---

## Next Steps

#### [Authentication](/guides/authentication)

Token management and refresh flows

#### [SDKs & Examples](/guides/sdks)

Pre-built error handling in helper functions

#### [Environments](/guides/environments)

Test error handling in development environment