> ## 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

> Error hierarchy, retry policy, and timeouts.

Every error thrown by the SDK is a subclass of `SpineError`, so a single
`catch (err instanceof SpineError)` clause catches anything the SDK can
throw.

## Error hierarchy

```text theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
SpineError
├── SpineConnectionError        # network / transport failures
├── SpineTimeoutError           # HTTP timeout or polling timeout
├── SpineRunFailedError         # waitForCompletion: status=failed
└── SpineAPIError               # 4xx / 5xx HTTP responses
    ├── SpineBadRequestError    # 400
    ├── SpineAuthError          # 401
    ├── SpineNotFoundError      # 404
    └── SpineServerError        # 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:

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { Spine, SpineBadRequestError } from 'spine-sdk';

try {
  const client = new Spine({ apiKey: process.env.SPINE_API_KEY! });
  await client.runs.create({ prompt: 'x', template: 'bogus' as 'memo' });
} catch (err) {
  if (err instanceof SpineBadRequestError) {
    console.log(err.statusCode);     // 400
    console.log(err.message);        // "Invalid template: bogus. Valid templates: ..."
    console.log(err.requestId);      // value of X-Request-ID header (if present)
    console.log(err.responseBody);   // parsed JSON body
  }
}
```

See [Errors](/api-reference/errors) for the full list of server-side
error messages.

## Client-side short-circuit

Some validation runs locally without a network round-trip so you get the
same typed error regardless of where the check fired:

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
await client.runs.create({ prompt: 'hi', blocks: [] });     // SpineBadRequestError
await client.runs.retrieve('not-a-uuid');                    // SpineBadRequestError
```

## Automatic retries

The SDK retries transient failures automatically with jittered
exponential backoff:

| Failure                               | Retried? | Notes               |
| ------------------------------------- | -------- | ------------------- |
| Connection errors (DNS, refused, TLS) | Yes      | Up to `maxRetries`. |
| HTTP 5xx                              | Yes      | Up to `maxRetries`. |
| HTTP 400 / 401 / 404                  | No       | Thrown immediately. |

When the retry budget is exhausted, the underlying error (`SpineServerError`
or `SpineConnectionError`) is thrown.

## Tuning retries

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
const client = new Spine({
  apiKey: process.env.SPINE_API_KEY!,
  maxRetries: 5,    // default: 2, set to 0 to disable retries entirely
});
```

## Timeouts

The **HTTP timeout** is 60 seconds per request by default. Override it
with `timeoutMs`:

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
const client = new Spine({
  apiKey: process.env.SPINE_API_KEY!,
  timeoutMs: 120_000,
});
```

The **polling timeout** (`waitForCompletion`) is independent and defaults
to 15 minutes. Tune it per call:

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
await client.runs.waitForCompletion(runId, { timeoutMs: 30 * 60_000 });
```

`SpineTimeoutError` is thrown when either timeout fires.

## `SpineRunFailedError`

Thrown by `waitForCompletion` when the run terminates with
`status: 'failed'`. Carries `runId` and an `errors: { error: string }[]`
list.

If you would rather handle the failed run as a value:

```ts theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
const terminal = await client.runs.waitForCompletion(runId, { resolveOnFailure: true });
if (terminal.status === 'failed') {
  console.error(terminal.errors);
}
```

A `'partial'` status is **not** a failure — the run produced usable
output with some blocks skipped. Inspect `terminal.errors` on the
returned object.
