---
title: Error codes
description: Reference for all API error codes and their meanings.
---

The Warmbly API uses standard HTTP status codes and returns structured error responses in JSON format.

## Error response format

All errors follow this structure:

```json
{
  "error": "Error Type",
  "message": "Human-readable description of what went wrong.",
  "code": "machine_readable_code",
  "request_id": "req_or_uuid_for_support"
}
```

`error` and `message` are for people. Client logic should use `code`, HTTP status, and endpoint-specific fields such as `retry_after`. Include `request_id` when contacting support.

## HTTP status codes

### Client errors (4xx)

| Code | Error | Description |
|------|-------|-------------|
| 400 | Bad Request | Invalid request syntax or parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 402 | Payment Required | Out of AI credits (`insufficient_credits`) |
| 403 | Forbidden | Authenticated but lacks permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource already exists |
| 422 | Unprocessable | Validation failed |
| 429 | Too Many Requests | Rate limit or AI usage cap exceeded (`rate_limit_exceeded`, `usage_cap_exceeded`) |

### Server errors (5xx)

| Code | Error | Description |
|------|-------|-------------|
| 500 | Internal Server Error | Unexpected server error |
| 501 | Not Implemented | Feature not available |
| 503 | Service Unavailable | Service temporarily down |

## Error details

### 400 Bad Request

Returned when the request cannot be processed due to invalid syntax.

**Common causes:**
- Invalid JSON in request body
- Missing required fields
- Invalid field types
- Values outside allowed ranges

**Example:**

```json
{
  "error": "Bad Request",
  "message": "invalid request body",
  "code": "bad_request",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Check that your JSON is valid
- Verify all required fields are present
- Ensure field values match expected types

### 401 Unauthorized

Returned when authentication fails.

**Common causes:**
- Missing `Authorization` header
- Invalid API key format
- Expired API key
- Revoked API key

**Example:**

```json
{
  "error": "Unauthorized",
  "message": "Token not found.",
  "code": "unauthorized",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Include the `Authorization: Bearer wmbly_...` header
- Verify your API key is correct
- Check if your key has expired or been revoked
- Generate a new key if necessary

### 402 Payment Required

Returned when an AI action is requested but the organization is out of credits. The response carries the stable code `insufficient_credits`.

```json
{
  "error": "Payment Required",
  "message": "You're out of AI credits. Add more to keep using the assistant.",
  "code": "insufficient_credits",
  "request_id": "req_..."
}
```

**How to fix:**
- Wait for the monthly allowance to reset, or buy a top-up pack (see [AI credits](/guides/ai-credits/))
- Related: a `429` with code `usage_cap_exceeded` means a short-term AI usage cap was hit; retry later

### 403 Forbidden

Returned when authenticated but lacking necessary permissions.

**Common causes:**
- API key lacks required permission
- Request IP not in allowlist
- Email account not in allowlist
- Organization access restricted

**Example:**

```json
{
  "error": "Forbidden",
  "message": "You don't have access to this feature.",
  "code": "forbidden",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Check your API key's permissions
- Verify IP restrictions if configured
- Request additional permissions if needed

### 404 Not Found

Returned when the requested resource doesn't exist.

**Common causes:**
- Invalid resource ID
- Resource was deleted
- Resource belongs to different organization
- Typo in endpoint URL

**Example:**

```json
{
  "error": "Not Found",
  "message": "Resource not found.",
  "code": "not_found",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Verify the resource ID is correct
- Check that the resource hasn't been deleted
- Ensure you're using the correct endpoint

### 409 Conflict

Returned when the request conflicts with existing data.

**Common causes:**
- Trying to create a resource that already exists
- Duplicate unique values

**Example:**

```json
{
  "error": "Conflict",
  "message": "resource already exists",
  "code": "conflict",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

### 422 Unprocessable

Returned when validation fails on the request data.

**Common causes:**
- Invalid email format
- String exceeds maximum length
- Number outside valid range
- Invalid enum value

**Example:**

```json
{
  "error": "Unprocessable",
  "message": "validation failed",
  "code": "unprocessable",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

### 500 Internal Server Error

Returned when an unexpected error occurs on the server.

**Example:**

```json
{
  "error": "Internal Server Error",
  "message": "Something went wrong.",
  "code": "internal_error",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Retry the request after a short delay
- If persistent, contact support with request details

### 503 Service Unavailable

Returned when the service is temporarily unavailable.

**Example:**

```json
{
  "error": "Service Unavailable",
  "message": "service unavailable",
  "code": "service_unavailable",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

**How to fix:**
- Wait and retry with exponential backoff
- Check status page for incidents

## Error handling best practices

### Implement retry logic

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

```javascript
async function requestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.ok) {
        return response.json();
      }

      // Don't retry client errors (4xx) except rate limits
      if (response.status >= 400 && response.status < 500 && response.status !== 429) {
        throw new Error(`Client error: ${response.status}`);
      }

      // Retry server errors and rate limits
      if (attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
    }
  }
}
```

### Parse error responses

Always parse and handle error responses:

```javascript
async function apiRequest(url, options) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error = await response.json();
    throw new ApiError(response.status, error.error, error.message);
  }

  return response.json();
}

class ApiError extends Error {
  constructor(status, type, message) {
    super(message);
    this.status = status;
    this.type = type;
  }
}
```

## Rate limiting

When you exceed rate limits, you'll receive:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 60
```

```json
{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Please retry after 60 seconds.",
  "code": "rate_limit_exceeded",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}
```

Use the `Retry-After` header to determine when to retry.

## See also

- [Authentication](/api/authentication/)
- [Endpoints](/api/endpoints/)
