# Error Handling

> Handle SDK errors gracefully

The SDK provides typed exceptions for different error scenarios.

## Exception Hierarchy

```
QualiaError
├── AuthenticationError
└── QualiaAPIError
    ├── NotFoundError
    ├── ValidationError
    └── RateLimitError
```

## Usage

```python
from qualia import (
    Qualia,
    QualiaError,
    QualiaAPIError,
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
)

try:
    client = Qualia(api_key="invalid-key")
    client.models.types()
except AuthenticationError as e:
    print(f"Auth failed: {e}")
except NotFoundError as e:
    print(f"Not found: {e}")
except ValidationError as e:
    print(f"Validation error: {e}")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after}s")
except QualiaAPIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
except QualiaError as e:
    print(f"SDK error: {e}")
```

## Exception Details

| Exception             | Raised for                                                                              |
| --------------------- | --------------------------------------------------------------------------------------- |
| `QualiaError`         | Base exception for all SDK errors; also a request that got no answer (timeout, network) |
| `QualiaAPIError`      | Any other error status; carries `status_code`, `message` and `code`                     |
| `AuthenticationError` | 401 or 403: an invalid or missing API key, or one without access                        |
| `NotFoundError`       | 404: the resource doesn't exist                                                         |
| `ValidationError`     | 400 or 422: invalid request parameters; `errors` holds the per-field list on a 422      |
| `RateLimitError`      | 429: too many requests; `retry_after` is the server's Retry-After in seconds            |

## Retries

A 429 on a `GET`, `HEAD`, `OPTIONS`, `PUT` or `DELETE` is sent again after its
Retry-After (at least 1 second; 1, 2 and 4 seconds without one), up to three
times while the server asks for at most 60 seconds; only then is
`RateLimitError` raised. A `POST` or `PATCH` that gets a 429 is never sent
again: the error is raised at once, so your code decides whether repeating it
is safe.

The long waits, `client.checks.wait` and `client.task_drafts.wait` (and the
CLI's `--wait` on them), sit out up to five failed reads in a row (any 5xx, a
408, a timeout or a dropped connection), backing off between them. Any other
4xx ends the wait at once, a 429 included: its request was already retried as
above, never past the wait's timeout. Neither stops the run, which keeps going server-side.
