Skip to main content
Every exception raised by the SDK is a subclass of SpineError, so a single except SpineError: clause catches anything the SDK can throw.

Exception hierarchy

SpineError
├── SpineConnectionError       # network / transport failures
├── SpineTimeoutError          # HTTP timeout or polling timeout
└── SpineAPIError              # 4xx / 5xx HTTP responses
    ├── AuthenticationError    # 401
    ├── BadRequestError        # 400
    ├── NotFoundError          # 404
    ├── RateLimitError         # 429 (after retry budget exhausted)
    └── ServerError            # 5xx (after retry budget exhausted)

Inspecting errors

SpineAPIError carries the HTTP status, the server’s error message, the request id (useful when reporting issues), and the raw body:
from spine import SpineClient, BadRequestError

try:
    with SpineClient() as client:
        client.runs.create(prompt="x", template="bogus")
except BadRequestError as exc:
    print(exc.status_code)      # 400
    print(exc.error_message)    # "Invalid template: bogus. Valid templates: ..."
    print(exc.request_id)       # value of X-Request-ID header
    print(exc.response_body)    # parsed JSON body
See Errors for the full list of server-side error messages.

Automatic retries

The SDK retries transient failures automatically with exponential backoff:
FailureRetried?Notes
Connection errors (DNS, refused, TLS)YesUp to max_retries.
HTTP 429YesHonours Retry-After header.
HTTP 5xxYesUp to max_retries.
HTTP 400 / 401 / 404NoRaised immediately.
When the retry budget is exhausted, the underlying exception (ServerError, RateLimitError, SpineConnectionError) is raised.

Tuning the policy

Override the policy on the client:
from spine import SpineClient, RetryPolicy

client = SpineClient(
    retry_policy=RetryPolicy(
        max_retries=5,      # default: 3
        base_delay=1.0,     # default: 0.5
        max_delay=60.0,     # default: 30.0
        jitter=0.2,         # ±20% jitter, default: 0.1
    ),
)
Set max_retries=0 to disable retries entirely — useful in tests.

Timeouts

The HTTP timeout is 60 seconds total / 10 seconds to connect by default. Override it with an httpx.Timeout:
import httpx
from spine import SpineClient

client = SpineClient(timeout=httpx.Timeout(120.0, connect=5.0))
The polling timeout (handle.wait(timeout=...)) is independent and defaults to 15 minutes. Tune it per call:
result = handle.wait(timeout=1800)   # 30 minutes
SpineTimeoutError is raised when either timeout fires.