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

# Errors

Defendis API uses standard HTTP status codes and a JSON error envelope. This page explains how to handle errors safely in production, including which scenarios are retryable.

## Error response format

Most error responses use this shape:

```json theme={null}
{
  "error": "string",
  "message": "string (optional)"
}
```

Implementation principles:

<CardGroup cols={2}>
  <Card title="Treat errors as text">
    Treat error as a human-readable reason, not a stable enum.
  </Card>

  <Card title="Stay forward-compatible">
    Ignore unknown response fields for forward compatibility.
  </Card>

  <Card title="Protect secrets in logs">
    Never log secrets, especially the `Authorization` header.
  </Card>
</CardGroup>

## Status codes and retry actions

| Status code             | Meaning                                               | Retry strategy | Recommended action                                               |
| ----------------------- | ----------------------------------------------------- | -------------- | ---------------------------------------------------------------- |
| `400 Bad Request`       | Missing or invalid parameters/JSON body               | No             | Fix request shape or required inputs                             |
| `401 Unauthorized`      | Missing, invalid, or revoked API key                  | No             | Validate credentials and replace/re-enable key                   |
| `403 Forbidden`         | Authenticated but blocked by enterprise access policy | No             | Verify enterprise scope/eligibility and enterprise billing state |
| `409 Conflict`          | Workspace quota reached on write operations           | No             | Reduce usage or increase workspace quotas, then retry            |
| `404 Not Found`         | Unknown endpoint or resource                          | No             | Confirm endpoint path, method, and resource identifiers          |
| `429 Too Many Requests` | Rate limit exceeded                                   | Yes            | Retry with exponential backoff + jitter                          |
| `500/502/503/504`       | Transient server-side failure                         | Conditional    | Retry safe requests (`GET`) with backoff                         |

## Common examples

| Scenario                         | HTTP status             | Example error body                                                                                            |
| -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------- |
| Missing API key                  | `401 Unauthorized`      | `{ "error": "Missing API key" }`                                                                              |
| Out-of-scope request             | `403 Forbidden`         | `{ "error": "You don't have permission to view this" }`                                                       |
| Enterprise billing access denied | `403 Forbidden`         | `{ "error": "Enterprise billing access denied", "reason": "payment_unpaid", "status": "unpaid" }`             |
| Watchlist quota reached          | `409 Conflict`          | `{ "error": "quota_domains_reached", "metric": "domains", "used": 10, "limit": 10, "requestedIncrement": 1 }` |
| Rate limit exceeded              | `429 Too Many Requests` | `{ "error": "Rate limit exceeded" }`                                                                          |

### Backoff snippet

```js title="JavaScript backoff snippet" theme={null}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchGetWithBackoff(url, options = {}, { maxRetries = 5, baseDelayMs = 500 } = {}) {
  const retryableStatuses = new Set([429, 500, 502, 503, 504]);

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const res = await fetch(url, { ...options, method: "GET" });
      if (!retryableStatuses.has(res.status)) return res;

      if (attempt === maxRetries) return res;

      const delay = Math.min(30_000, baseDelayMs * 2 ** attempt);
      const jitter = Math.floor(Math.random() * 250);
      await sleep(delay + jitter);
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.min(30_000, baseDelayMs * 2 ** attempt);
      const jitter = Math.floor(Math.random() * 250);
      await sleep(delay + jitter);
    }
  }
}
```

```python title="Python backoff snippet" theme={null}
import random
import time
import requests

def get_with_backoff(url, headers, max_retries=5, base_delay_s=0.5, timeout_s=30):
    retryable_statuses = {429, 500, 502, 503, 504}

    for attempt in range(max_retries + 1):
        try:
            resp = requests.get(url, headers=headers, timeout=timeout_s)
        except requests.RequestException:
            if attempt == max_retries:
                raise
            resp = None

        if resp is not None and resp.status_code not in retryable_statuses:
            return resp

        if attempt == max_retries:
            return resp

        delay = min(30.0, base_delay_s * (2 ** attempt))
        jitter = random.uniform(0, 0.25)
        time.sleep(delay + jitter)
```

## Troubleshooting checklist

When contacting Support, include:

<Steps>
  <Step title="Request path">
    Include the request path `example: /api/v1/dataleaks/stats`
  </Step>

  <Step title="Request timing and correlation">
    Include the UTC timestamp and your client request ID.
  </Step>

  <Step title="Error details">
    Include the HTTP status code and the error response body.
  </Step>

  <Step title="Request parameters">
    Include the parameters you sent, with sensitive values redacted.
  </Step>
</Steps>
