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

# Errors

> Understand Bizzy API error codes and responses

The Bizzy API uses conventional HTTP status codes and returns detailed error
information in JSON format to help you handle errors gracefully.

## Error Response Format

All error responses follow a consistent structure:

```json theme={null}
{
    "error": {
        "code": "ERROR_CODE",
        "message": "A human-readable description of the error",
        "details": {
            // Additional context (optional)
        }
    }
}
```

| Field     | Type   | Description                                             |
| --------- | ------ | ------------------------------------------------------- |
| `code`    | string | A machine-readable error code for programmatic handling |
| `message` | string | A human-readable description of what went wrong         |
| `details` | object | Additional context about the error (optional)           |

## HTTP Status Codes

| Status | Meaning               | When It Occurs                                         |
| ------ | --------------------- | ------------------------------------------------------ |
| `200`  | OK                    | Request succeeded                                      |
| `201`  | Created               | Resource was created successfully                      |
| `204`  | No Content            | Request succeeded with no response body (e.g., DELETE) |
| `400`  | Bad Request           | Invalid request format or parameters                   |
| `401`  | Unauthorized          | Missing or invalid API key                             |
| `403`  | Forbidden             | Valid API key but insufficient permissions             |
| `404`  | Not Found             | Resource doesn't exist                                 |
| `429`  | Too Many Requests     | Rate limit exceeded                                    |
| `500`  | Internal Server Error | Something went wrong on our end                        |
| `503`  | Service Unavailable   | Temporary service outage                               |

## Error Codes Reference

### Authentication Errors

| Code                       | Status | Description                        |
| -------------------------- | ------ | ---------------------------------- |
| `UNAUTHORIZED`             | 401    | Invalid or missing API key         |
| `INSUFFICIENT_PERMISSIONS` | 403    | API key lacks required permissions |

```json theme={null}
{
    "error": {
        "code": "UNAUTHORIZED",
        "message": "Invalid or missing API key"
    }
}
```

### Validation Errors

| Code               | Status | Description                         |
| ------------------ | ------ | ----------------------------------- |
| `VALIDATION_ERROR` | 400    | Request body failed validation      |
| `INVALID_INPUT`    | 400    | Invalid query parameters or input   |
| `MISSING_QUERY`    | 400    | Required query parameter is missing |

Validation errors include details about which fields failed. `details` is a tree
mirroring the request body: each node has an `errors` array (issues at that
path), and object nodes additionally have a `properties` map keyed by field
name. The top-level `errors` array carries issues that apply to the request as a
whole.

```json theme={null}
{
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "Invalid request body",
        "details": {
            "errors": [],
            "properties": {
                "email": {
                    "errors": ["Invalid email address"]
                },
                "name": {
                    "errors": [
                        "Too small: expected string to have >=1 characters"
                    ]
                }
            }
        }
    }
}
```

### Resource Errors

| Code                  | Status | Description                      |
| --------------------- | ------ | -------------------------------- |
| `NOT_FOUND`           | 404    | Requested resource doesn't exist |
| `INVALID_MESSAGE_ID`  | 400    | Invalid message ID format        |
| `INVALID_CONTACT_ID`  | 400    | Invalid contact ID format        |
| `INVALID_BUSINESS_ID` | 400    | Invalid business ID format       |

```json theme={null}
{
    "error": {
        "code": "NOT_FOUND",
        "message": "Contact not found"
    }
}
```

<Note>
  For standard resources, `GET`, `PUT`/`PATCH`, and `DELETE` requests that
  target a resource which doesn't exist (or isn't visible to your
  organization) all return `404` with the `NOT_FOUND` code. Updating or
  deleting a missing resource is never a `500`.

  DNS record write operations
  (`PUT`/`DELETE /v1/domains/{id}/dns-records/{recordId}`) are the exception:
  they proxy Cloudflare, so a missing record surfaces the upstream status
  (typically `404`) with the `DNS_ERROR` code rather than `NOT_FOUND`.
</Note>

### Domain Management Errors

| Code                              | Status | Description                                             |
| --------------------------------- | ------ | ------------------------------------------------------- |
| `REGISTRAR_UNSUPPORTED_OPERATION` | 422    | The requested operation isn't available for this domain |

```json theme={null}
{
    "error": {
        "code": "REGISTRAR_UNSUPPORTED_OPERATION",
        "message": "WHOIS privacy is always enabled for this domain and cannot be disabled",
        "details": {
            "operation": "disabling WHOIS privacy"
        }
    }
}
```

<Note>
  A small number of management operations aren't available for every domain —
  for example, some domains have WHOIS privacy permanently enabled, so it
  can't be disabled. When that happens the request returns `422` with this
  code and the affected `operation`.
</Note>

### Rate Limiting Errors

| Code           | Status | Description       |
| -------------- | ------ | ----------------- |
| `RATE_LIMITED` | 429    | Too many requests |

```json theme={null}
{
    "error": {
        "code": "RATE_LIMITED",
        "message": "Rate limit exceeded. Retry after the number of seconds indicated by the Retry-After header."
    }
}
```

A `429` from your plan's rate limit carries `X-RateLimit-Limit`,
`X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers —
wait `Retry-After` seconds before retrying (fall back to exponential backoff
if the header is absent). See [Rate Limits](/api-reference/rate-limits) for
per-plan limits.

### Server Errors

| Code                  | Status | Description                     |
| --------------------- | ------ | ------------------------------- |
| `INTERNAL_ERROR`      | 500    | Unexpected server error         |
| `SERVICE_UNAVAILABLE` | 503    | Service temporarily unavailable |

```json theme={null}
{
    "error": {
        "code": "INTERNAL_ERROR",
        "message": "An unexpected error occurred"
    }
}
```

## Handling Errors

### Basic Error Handling

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function makeRequest() {
      const response = await fetch('https://api.bizzyco.ai/v1/contacts', {
          headers: {
              'Authorization': `Bearer ${apiKey}`,
          },
      });

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

          switch (response.status) {
              case 401:
                  throw new Error('Invalid API key');
              case 403:
                  throw new Error(`Permission denied: ${error.message}`);
              case 404:
                  throw new Error('Resource not found');
              case 429:
                  throw new Error('Rate limited - retry later');
              default:
                  throw new Error(error.message || 'Request failed');
          }
      }

      return response.json();

  }

  ```

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

  def make_request():
      response = requests.get(
          'https://api.bizzyco.ai/v1/contacts',
          headers={'Authorization': f'Bearer {api_key}'}
      )

      if not response.ok:
          error = response.json().get('error', {})

          if response.status_code == 401:
              raise Exception('Invalid API key')
          elif response.status_code == 403:
              raise Exception(f"Permission denied: {error.get('message')}")
          elif response.status_code == 404:
              raise Exception('Resource not found')
          elif response.status_code == 429:
              raise Exception('Rate limited - retry later')
          else:
              raise Exception(error.get('message', 'Request failed'))

      return response.json()
  ```
</CodeGroup>

### Retry with Exponential Backoff

For transient errors (429, 500, 503), implement retry logic with exponential
backoff:

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function fetchWithRetry(
      url: string,
      options: RequestInit,
      maxRetries = 3
  ): Promise<Response> {
      let lastError: Error;

      for (let attempt = 0; attempt < maxRetries; attempt++) {
          try {
              const response = await fetch(url, options);

              // Don't retry client errors (except rate limiting)
              if (response.ok || (response.status >= 400 && response.status < 500 && response.status !== 429)) {
                  return response;
              }

              // For rate limiting, use Retry-After header if available
              if (response.status === 429) {
                  const retryAfter = response.headers.get('Retry-After');
                  const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
                  await new Promise(resolve => setTimeout(resolve, delay));
                  continue;
              }

              // For server errors, use exponential backoff
              if (response.status >= 500) {
                  const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
                  await new Promise(resolve => setTimeout(resolve, delay));
                  continue;
              }

              return response;
          } catch (error) {
              lastError = error as Error;
              const delay = Math.pow(2, attempt) * 1000;
              await new Promise(resolve => setTimeout(resolve, delay));
          }
      }

      throw lastError!;

  }

  ```

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

  def fetch_with_retry(url, headers, max_retries=3):
      last_error = None

      for attempt in range(max_retries):
          try:
              response = requests.get(url, headers=headers)

              # Don't retry client errors (except rate limiting)
              if response.ok or (400 <= response.status_code < 500 and response.status_code != 429):
                  return response

              # For rate limiting, use Retry-After header if available
              if response.status_code == 429:
                  retry_after = response.headers.get('Retry-After')
                  delay = int(retry_after) if retry_after else (2 ** attempt)
                  time.sleep(delay)
                  continue

              # For server errors, use exponential backoff
              if response.status_code >= 500:
                  delay = 2 ** attempt  # 1s, 2s, 4s
                  time.sleep(delay)
                  continue

              return response
          except requests.RequestException as e:
              last_error = e
              delay = 2 ** attempt
              time.sleep(delay)

      raise last_error
  ```
</CodeGroup>

## Best Practices

<AccordionGroup>
  <Accordion title="Always check the error code">
    Use the `code` field for programmatic error handling, not the `message`. Error messages may change, but error codes remain stable.
  </Accordion>

  <Accordion title="Log errors with context">
    Include the request ID from response headers (`X-Request-ID`) when logging
    errors to help with debugging and support requests.
  </Accordion>

  <Accordion title="Handle validation errors gracefully">
    Walk the `details` tree (each node's `errors` array, recursing into `properties`) to show specific field errors to your users rather than generic error messages.
  </Accordion>
</AccordionGroup>
