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

# Error Handling

> How to handle errors returned by the API

## Error Structure

When an error occurs, the API returns a response with the following structure:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Error description",
    "details": {
      // Additional information when available
    }
  }
}
```

## Error Codes

### Authentication Errors

<AccordionGroup>
  <Accordion title="UNAUTHORIZED" icon="lock">
    **Status:** 401

    The API key is missing, invalid, or expired.

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

    **Solution:** Verify that the key is correct and not expired.
  </Accordion>

  <Accordion title="FORBIDDEN" icon="ban">
    **Status:** 403

    The key doesn't have permission for this resource.

    ```json theme={null}
    {
      "error": {
        "code": "FORBIDDEN",
        "message": "You don't have permission to access this resource"
      }
    }
    ```

    **Solution:** Check your key permissions in the dashboard.
  </Accordion>
</AccordionGroup>

### Validation Errors

<AccordionGroup>
  <Accordion title="VALIDATION_ERROR" icon="circle-exclamation">
    **Status:** 400

    The submitted data is invalid.

    ```json theme={null}
    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "Invalid request data",
        "details": {
          "field": "email",
          "reason": "Invalid email format"
        }
      }
    }
    ```

    **Solution:** Fix the fields indicated in `details`.
  </Accordion>

  <Accordion title="MISSING_FIELD" icon="asterisk">
    **Status:** 400

    A required field was not sent.

    ```json theme={null}
    {
      "error": {
        "code": "MISSING_FIELD",
        "message": "Required field missing",
        "details": {
          "field": "phone"
        }
      }
    }
    ```

    **Solution:** Include the required field in the request.
  </Accordion>
</AccordionGroup>

### Resource Errors

<AccordionGroup>
  <Accordion title="NOT_FOUND" icon="magnifying-glass">
    **Status:** 404

    The requested resource doesn't exist.

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

    **Solution:** Verify that the ID is correct.
  </Accordion>

  <Accordion title="ALREADY_EXISTS" icon="clone">
    **Status:** 409

    A resource with the same data already exists.

    ```json theme={null}
    {
      "error": {
        "code": "ALREADY_EXISTS",
        "message": "A lead with this phone number already exists"
      }
    }
    ```

    **Solution:** Use the update endpoint or use different data.
  </Accordion>
</AccordionGroup>

### Rate Limit Errors

<AccordionGroup>
  <Accordion title="RATE_LIMIT_EXCEEDED" icon="gauge-high">
    **Status:** 429

    Too many requests in a short time.

    ```json theme={null}
    {
      "error": {
        "code": "RATE_LIMIT_EXCEEDED",
        "message": "Too many requests",
        "details": {
          "retry_after": 60
        }
      }
    }
    ```

    **Solution:** Wait for the time indicated in `retry_after` (seconds).
  </Accordion>
</AccordionGroup>

## Implementing Retry Logic

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

        if (response.status === 429) {
          const data = await response.json();
          const retryAfter = data.error?.details?.retry_after || 60;
          console.log(`Rate limited. Retrying in ${retryAfter}s...`);
          await new Promise(r => setTimeout(r, retryAfter * 1000));
          continue;
        }

        if (response.status >= 500) {
          const delay = Math.pow(2, attempt) * 1000;
          console.log(`Server error. Retrying in ${delay}ms...`);
          await new Promise(r => setTimeout(r, delay));
          continue;
        }

        return response;
      } catch (error) {
        if (attempt === maxRetries - 1) throw error;

        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }
  ```

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

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

              if response.status_code == 429:
                  retry_after = response.json().get('error', {}).get('details', {}).get('retry_after', 60)
                  print(f"Rate limited. Retrying in {retry_after}s...")
                  time.sleep(retry_after)
                  continue

              if response.status_code >= 500:
                  delay = (2 ** attempt)
                  print(f"Server error. Retrying in {delay}s...")
                  time.sleep(delay)
                  continue

              return response

          except requests.RequestException as e:
              if attempt == max_retries - 1:
                  raise
              time.sleep(2 ** attempt)

      raise Exception("Max retries exceeded")
  ```
</CodeGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Always Check Status" icon="check">
    Never assume the request was successful. Always check the HTTP code.
  </Card>

  <Card title="Implement Retry" icon="rotate">
    For 5xx and 429 errors, implement retry with exponential backoff.
  </Card>

  <Card title="Log Errors" icon="file-lines">
    Record errors for debugging and monitoring.
  </Card>

  <Card title="Handle Specific Cases" icon="code-branch">
    Implement specific handling for each error type.
  </Card>
</CardGroup>
