Error handling

Every error raised by the SDK derives from VerdifaxError. Catch the base class for a single safety net, or catch specific subclasses to branch on failure mode.

Hierarchy

VerdifaxError
├── ValidationError       client-side input validation failed
├── ConnectionError       transport-level failure (DNS / TCP / TLS / timeout)
└── APIError              non-2xx HTTP response
    └── StageError        a specific pipeline stage rejected the run

Import

from verdifax import (
    VerdifaxError,
    ValidationError,
    ConnectionError,
    APIError,
    StageError,
)

Note that verdifax.ConnectionError is not the builtin ConnectionError from OSError, it intentionally doesn't inherit from OSError so a generic except OSError doesn't silently swallow Verdifax errors.

When each is raised

ExceptionWhenAction
ValidationErrorbad hex, empty route, wrong type, malformed payloadFix the input, never retry
ConnectionErrorDNS failure, connection refused, timeoutRetry with backoff
StageErrorThe orchestrator's pipeline rejected the run; check .stageInvestigate stage; usually a config or auth issue
APIError5xx, 4xx other than the aboveRetry on 5xx; investigate on 4xx

Branching example

from verdifax import StageError, ConnectionError, ValidationError, APIError

try:
    receipt = client.attest(...)
except ValidationError as e:
    log.error("bad input", error=str(e))
    raise  # don't retry, caller bug
except StageError as e:
    log.error("stage rejected", stage=e.stage, status=e.status_code, error=e.message)
    # could be auth issue, or upstream input that doesn't pass the stage check
    raise
except ConnectionError as e:
    log.warning("transient, retrying", error=str(e))
    raise  # let your retry layer handle it
except APIError as e:
    if 500 <= e.status_code < 600:
        log.warning("server error, retrying", status=e.status_code)
        raise
    log.error("client error", status=e.status_code, error=e.message)
    raise

Retry only on ConnectionError and APIError with status_code >= 500. Use exponential backoff. Never retry ValidationError or StageError.

from tenacity import retry, retry_if_exception_type, wait_exponential, stop_after_attempt

@retry(
    retry=retry_if_exception_type((ConnectionError,)),
    wait=wait_exponential(multiplier=1, min=1, max=30),
    stop=stop_after_attempt(5),
)
def attest_with_retries(client, **kwargs):
    return client.attest(**kwargs)

Continue