Errors

The SDK's exception hierarchy — catch broadly, act specifically.

Hierarchy

NevoError
├── AuthError                 # 401 — token invalid
├── ConnectionError           # WS couldn't connect after retries
├── HandlerTimeoutError       # handler exceeded handler_timeout
└── ReplyError                # anything reply-related
    ├── UnsupportedChannelError
    ├── NetworkError          # transient
    ├── ServerError           # transient (5xx)
    └── RequestError          # permanent (4xx / validation)

Catching strategy

from nevo import NevoError, AuthError, ReplyError

try:
    await client.run()
except AuthError:
    # Fix the key and restart.
    ...
except NevoError as exc:
    # Catch-all — log and exit.
    ...

For replies specifically:

try:
    await event.reply(text="…")
except ReplyError as exc:
    if exc.is_transient:
        # Enqueue for retry.
        ...
    else:
        # Log and drop — retrying won't help.
        ...

Error metadata

Every ReplyError carries:

exc.status_code   # int | None — HTTP status from the server
exc.code          # str | None — server-provided error code (e.g. "channel_unsupported")
exc.request_id    # str | None — X-Request-ID for support correlation
exc.is_transient  # bool — True for NetworkError / ServerError

str(exc) includes all of these when set:

invalid api key | status=401 | code=unauthorized | request_id=req-abc123

Token redaction

The SDK never includes the bearer token in exception messages, logs, or tracebacks. The Authorization header is held inside the httpx.AsyncClient and isn’t copied onto exceptions.

Handler exceptions

If your handler raises, the SDK logs the traceback at ERROR and continues consuming the stream. The event is not re-delivered — Nevo’s redelivery is dashboard-driven replay, not client-side retry. Structure your handler to be idempotent if that matters.