VerdifaX

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