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

# Python SDK - Error Handling

> Error hierarchy and when each exception is raised in the Python SDK.

The SDK distinguishes between two classes of failures:

1. **Initial sync failures** - surfaced as exceptions from `NexusClient.create()` / `await AsyncNexusClient.create()`. The application should refuse to start with an empty cache.
2. **Background sync / SSE failures** - never raised. They are logged via `NexusLogger.error(...)`, and the existing cache continues to be served.

## Error hierarchy

All SDK exceptions extend `NexusError`. A single `except` clause covers everything:

```python theme={null}
from westyx_nexus import NexusError

try:
    secret = nexus.get_secret("DB_PASSWORD")
except NexusError as e:
    # SDK-specific failure - handle as needed
    ...
```

The full hierarchy:

```
Exception
└── NexusError                          (base class)
    ├── NexusInitError                  (initial sync failure - __cause__ has detail)
    ├── NexusUnauthorizedError          (HTTP 401)
    ├── NexusBillingError               (HTTP 402 - v0.2.0)
    ├── NexusNotFoundError              (HTTP 404)
    ├── NexusQuarantinedError           (HTTP 429 quarantine - v0.4.0)
    ├── NexusRateLimitedError           (HTTP 429 write rate limit - v0.5.0)
    ├── NexusPublicKeyError             (get_secret called with public key)
    ├── NexusServiceKindMismatchError   (get_secret on frontend service - v0.2.0)
    ├── NexusSecretNotFoundError        (key absent from cache)
    └── NexusAbAddonNotAvailableError   (evaluate_ab - HTTP 403 - v0.3.0)
```

## When each is raised

| Error                                         | Raised by                                                                    | When                                                                                                                                                                                        |
| --------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NexusInitError`                              | `NexusClient.create()`, `AsyncNexusClient.create()`                          | The initial sync failed for any reason. The `__cause__` attribute carries the underlying exception (often `NexusUnauthorizedError` or an `httpx.HTTPError`).                                |
| `NexusUnauthorizedError`                      | `create()`, `sync()`                                                         | Server returned HTTP 401 - invalid API key, slug/key mismatch, or revoked key.                                                                                                              |
| `NexusBillingError` *(v0.2.0)*                | `create()`, `sync()`                                                         | Server returned HTTP 402 - tenant has invoices overdue by more than 14 days. Background sync halts; the cache continues to serve. Check `client.billing_overdue`.                           |
| `NexusQuarantinedError` *(v0.4.0)*            | `sync()`                                                                     | Server returned HTTP 429 with `{"error":"quarantined","reason":"...","expires_at":"..."}`. Background sync is paused until `expires_at`. The `on_quarantined` observer callback also fires. |
| `NexusRateLimitedError` *(v0.5.0)*            | `set_secret()`, `delete_secret()`, `delete_secret_version()`                 | Server returned HTTP 429 - write rate limit exceeded on secret modification endpoints.                                                                                                      |
| `NexusNotFoundError`                          | `create()`, `sync()`                                                         | Server returned HTTP 404 - unknown subdomain or missing endpoint.                                                                                                                           |
| `NexusPublicKeyError`                         | `get_secret()`, `set_secret()`, `delete_secret()`, `delete_secret_version()` | The client uses a public key. Raised synchronously, before the network call.                                                                                                                |
| `NexusServiceKindMismatchError` *(v0.2.0)*    | `get_secret()`                                                               | The service `kind` reported by the server is `"frontend"`. Frontend services must not hold secrets, regardless of key type.                                                                 |
| `NexusSecretNotFoundError`                    | `get_secret()`                                                               | The requested key is not in the cache (and the client uses an secret key). The `key` attribute carries the requested name.                                                                  |
| `NexusWIFNotConfiguredError` *(v0.2.0)*       | `create()`                                                                   | `wif=WIFConfig(enabled=True)` but no usable workload-identity provider matched the running environment. Wrapped in `NexusInitError` on the initial connect.                                 |
| `NexusWIFTokenExchangeFailedError` *(v0.2.0)* | `create()`, `sync()`                                                         | `POST /v1/auth/token-exchange` returned a non-2xx response, or the body could not be decoded.                                                                                               |
| `NexusSessionExpiredError` *(v0.2.0)*         | `sync()`                                                                     | The WIF session JWT has expired and the SDK could not refresh it.                                                                                                                           |
| `NexusAbAddonNotAvailableError` *(v0.3.0)*    | `evaluate_ab()`                                                              | Server returned HTTP 403 - the AB Testing add-on is not active for this tenant/service.                                                                                                     |

## What is NOT raised

* **Background syncs** - silently retried on the next TTL tick. Cache served.
* **SSE transport errors** - exponential backoff, 3-strike fallback to polling. No exception surfaces.
* **`304 Not Modified`** - treated as success; TTL reset.
* **Missing config / flag** - `get_config` returns the `default` (or `None`); `get_flag` returns the `default` (or `False`).

## Failure scenarios

### Wrong API key

```python theme={null}
from westyx_nexus import (
    NexusClient,
    NexusConfig,
    NexusInitError,
    NexusUnauthorizedError,
)

try:
    nexus = NexusClient.create(NexusConfig(base_url="...", api_key="wxs_typo"))
except NexusInitError as e:
    if isinstance(e.__cause__, NexusUnauthorizedError):
        print("nexus: bad credentials - check NEXUS_API_KEY")
        sys.exit(1)
    raise
```

### Network down at startup

```python theme={null}
import time
import httpx

def create_with_retry(config, retries=5):
    for i in range(retries):
        try:
            return NexusClient.create(config)
        except NexusInitError as e:
            if i == retries - 1:
                raise
            print(f"nexus: retry {i + 1}: {e}")
            time.sleep(2 ** i)
    raise RuntimeError("unreachable")
```

### Reading a secret with a public key

```python theme={null}
from westyx_nexus import NexusPublicKeyError

try:
    nexus.get_secret("DB_PASSWORD")
except NexusPublicKeyError:
    print("nexus: public keys cannot read secrets - use a secret key")
```

### Reading a secret that doesn't exist

```python theme={null}
from westyx_nexus import NexusSecretNotFoundError

try:
    value = nexus.get_secret("does.not.exist")
except NexusSecretNotFoundError as e:
    print(f"missing secret: {e.key}")
```

### Reading a non-existent flag or config

Returns the default - no exception:

```python theme={null}
nexus.get_flag("made.up.key", default=False)   # False
nexus.get_config("made.up.key")                 # None
nexus.get_config("made.up.key", default="fallback")   # "fallback"
```

## Async-specific notes

The async client uses the **same exception hierarchy**. The only difference is that `await AsyncNexusClient.create(...)` is a coroutine, and `async with` is the equivalent of the sync `with` statement.

```python theme={null}
async def main() -> None:
    try:
        async with await AsyncNexusClient.create(config) as nexus:
            secret = nexus.get_secret("key")    # not async - but still raises
    except NexusInitError as e:
        # handle init failure
        ...
```
