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

# Python Error Handling

> Handle exceptions and retries with the DevHelm Python SDK

The SDK raises typed exceptions for API failures, validation issues, and network problems. The taxonomy mirrors the API's `ErrorResponse` envelope, with subclasses per HTTP class for ergonomic catching.

## Exception hierarchy

```
DevhelmError                  ← umbrella; catch as a fallback
├── DevhelmValidationError    ← local request/response shape validation
├── DevhelmTransportError     ← connection refused, DNS, TLS, timeout
└── DevhelmApiError           ← non-2xx HTTP response
    ├── DevhelmAuthError      ← 401, 403
    ├── DevhelmNotFoundError  ← 404
    ├── DevhelmConflictError  ← 409
    ├── DevhelmRateLimitError ← 429
    └── DevhelmServerError    ← 5xx
```

All classes are importable from `devhelm`:

```python theme={null}
from devhelm import (
    DevhelmError,
    DevhelmValidationError,
    DevhelmApiError,
    DevhelmAuthError,
    DevhelmNotFoundError,
    DevhelmConflictError,
    DevhelmRateLimitError,
    DevhelmServerError,
    DevhelmTransportError,
)
```

## DevhelmApiError

The most common error class — raised for any non-2xx HTTP response.

| Attribute    | Type                  | Description                                                                                                                                 |
| ------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`     | `int`                 | HTTP status code (e.g. `404`)                                                                                                               |
| `code`       | `str`                 | Coarse machine-readable category from `ErrorResponse.code` (e.g. `"NOT_FOUND"`). Defaults to `"API_ERROR"` if the server didn't supply one. |
| `message`    | `str`                 | Human-readable message — for logs and user-facing copy                                                                                      |
| `detail`     | `str \| None`         | Additional context, when available                                                                                                          |
| `body`       | `dict \| str \| None` | Raw parsed response body for debugging                                                                                                      |
| `request_id` | `str \| None`         | Per-request id from the `X-Request-Id` response header. **Always include in support tickets.**                                              |

```python theme={null}
from devhelm import DevhelmApiError

try:
    client.monitors.get("missing")
except DevhelmApiError as e:
    print(e.status)       # 404
    print(e.code)         # "NOT_FOUND"
    print(e.request_id)   # "req_01HJV..."
    print(e.message)
```

## DevhelmValidationError

Raised before HTTP I/O when a request body fails Pydantic validation, and after a successful response when the response body doesn't match the expected schema (rare — usually indicates a stale SDK).

| Attribute | Type   | Description                                            |
| --------- | ------ | ------------------------------------------------------ |
| `message` | `str`  | What failed validation                                 |
| `errors`  | `list` | Pydantic-style error records with `loc`, `msg`, `type` |

```python theme={null}
from devhelm import DevhelmValidationError

try:
    client.monitors.create({"type": "HTTP"})  # missing required fields
except DevhelmValidationError as e:
    for err in e.errors:
        print(f"{err['loc']}: {err['msg']}")
```

## DevhelmTransportError

Wraps `httpx` network failures (connection refused, DNS, TLS handshake, timeout) so callers don't need to import `httpx`. The original exception is on `__cause__`.

```python theme={null}
from devhelm import DevhelmTransportError

try:
    client.monitors.list()
except DevhelmTransportError as e:
    print(f"Network error: {e}")
    print(f"Underlying: {e.__cause__!r}")
```

## Handling patterns

### Catch by HTTP class

```python theme={null}
from devhelm import (
    DevhelmAuthError, DevhelmNotFoundError, DevhelmRateLimitError, DevhelmError,
)

try:
    monitor = client.monitors.get(monitor_id)
except DevhelmAuthError:
    print("Bad token or insufficient permissions")
except DevhelmNotFoundError:
    print(f"Monitor {monitor_id} no longer exists")
except DevhelmRateLimitError as e:
    print(f"Rate limited (request_id={e.request_id})")
except DevhelmError:
    raise
```

### Retry on rate limits and 5xx

```python theme={null}
import time
from devhelm import DevhelmRateLimitError, DevhelmServerError, DevhelmTransportError

def with_retry(fn, *, max_retries=3, base_delay=1.0):
    for attempt in range(max_retries + 1):
        try:
            return fn()
        except (DevhelmRateLimitError, DevhelmServerError, DevhelmTransportError) as e:
            if attempt == max_retries:
                raise
            delay = base_delay * (2 ** attempt)
            time.sleep(delay)

monitors = with_retry(client.monitors.list)
```

<Note>
  Don't retry on `DevhelmValidationError`, `DevhelmAuthError`, `DevhelmNotFoundError`, or `DevhelmConflictError` — these are deterministic and won't change without code or config changes.
</Note>

### Deploy lock contention

```python theme={null}
from devhelm import DevhelmConflictError

try:
    lock = client.deploy_lock.acquire({
        "lockedBy": "ci-pipeline",
        "ttlMinutes": 30,
    })
except DevhelmConflictError:
    current = client.deploy_lock.current()
    if current:
        print(f"Lock held by {current.locked_by} (since {current.locked_at})")
```

### Catch-all logging

```python theme={null}
from devhelm import DevhelmApiError, DevhelmError

try:
    do_work(client)
except DevhelmApiError as e:
    log.error(
        "DevHelm API error",
        extra={
            "status": e.status,
            "code": e.code,
            "request_id": e.request_id,
            "detail": e.detail,
        },
    )
except DevhelmError:
    log.exception("DevHelm SDK error")
```

## Next steps

<CardGroup cols={2}>
  <Card title="Client reference" icon="book" href="/sdk/python/client-reference">
    Full method reference for all resources.
  </Card>

  <Card title="Error patterns" icon="book" href="/patterns/errors">
    Common error scenarios across all surfaces.
  </Card>
</CardGroup>
