Skip to content

Errors

Everything this SDK raises descends from SystemOneError, so one except catches the lot:

SystemOneError                 # catch this
├── APIError                   # non-2xx; .status .body .retry_after
│   └── AuthenticationError    # 401/403, never retried
└── APIConnectionError
    └── APITimeoutError
from system_one import SystemOne, SystemOneError

try:
    response = agent.ask(state, questions)
except SystemOneError:
    fall_back_to_the_rules_engine()

APIError

Raised for any non-2xx response. Inspect status rather than catching a per-status subclass:

except APIError as exc:
    exc.status        # the HTTP status
    exc.body          # the response body, when there was one
    exc.retry_after   # seconds, parsed from the Retry-After header

AuthenticationError is the one subclass — a 401 or 403. It is a configuration problem (missing or wrong SYSTEM_ONE_API_KEY), so it is never retried and fails fast.

APIConnectionError

The request never reached the server. APITimeoutError is the subclass for "did not complete within SYSTEM_ONE_TIMEOUT". Both also inherit from the builtins you would expect — ConnectionError and TimeoutError respectively — so existing except ConnectionError handlers keep working.

Retries

The HTTP backend retries automatically, up to SYSTEM_ONE_MAX_RETRIES (default 2) with exponential backoff:

Retried Not retried
408, 429, 500, 502, 503, 504 every other non-2xx
connection errors and timeouts 401, 403

Retry-After is honoured when the server sends it. The exported RETRY_STATUSES frozenset is the exact set, if you need to branch on it.

Set SYSTEM_ONE_MAX_RETRIES=0 to disable retries — useful when a caller upstream already has its own budget.

Validation errors

A malformed response body raises pydantic's ValidationError, not a SystemOneError. That is deliberate: it means the provider sent something that does not match the contract, which is a different problem from the request failing.