> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getspine.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and retries

> Exception hierarchy, retry policy, and timeouts.

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

```text theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
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:

```python theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
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](/api-reference/errors) for the full list of server-side
error messages.

## Automatic retries

The SDK retries transient failures automatically with exponential
backoff:

| Failure                               | Retried? | Notes                         |
| ------------------------------------- | -------- | ----------------------------- |
| Connection errors (DNS, refused, TLS) | Yes      | Up to `max_retries`.          |
| HTTP 429                              | Yes      | Honours `Retry-After` header. |
| HTTP 5xx                              | Yes      | Up to `max_retries`.          |
| HTTP 400 / 401 / 404                  | No       | Raised immediately.           |

When the retry budget is exhausted, the underlying exception
(`ServerError`, `RateLimitError`, `SpineConnectionError`) is raised.

## Tuning the policy

Override the policy on the client:

```python theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
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`:

```python theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
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:

```python theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
result = handle.wait(timeout=1800)   # 30 minutes
```

`SpineTimeoutError` is raised when either timeout fires.
