# Errors & retries

## The error types

Everything the SDK throws extends `LayerError`, so one `catch` covers it.

| Class                    | When                                                                      |
| ------------------------ | ------------------------------------------------------------------------- |
| LayerApiError            | The API answered with a problem document                                  |
| LayerRunFailedError      | A generation or training run reached a terminal state that is not success |
| LayerBudgetExceededError | A run was priced above maxCreativeUnits, so nothing was submitted         |
| LayerTimeoutError        | A request, or a wait for a run, outlived its deadline                     |
| LayerConnectionError     | The request never got an answer                                           |

## Branching on a failure

Branch on `code` — the machine-readable identifier — never on the message, which is prose and may be reworded:

```ts
import { LayerApiError, LayerRunFailedError } from '@layer_ai/sdk'


try {
  await layer.inferences.generate({ prompt: '…' })
} catch (error) {
  if (error instanceof LayerRunFailedError && error.isInsufficientBalance) {
    // The workspace ran out of Creative Units. Top up and resubmit.
  }
  if (error instanceof LayerApiError && error.code === 'CONTENT_POLICY_VIOLATION') {
    // The prompt was refused. Rewriting it is the fix; retrying it is not.
  }
  throw error
}
```

A `LayerApiError` carries the whole problem document — `status`, `code`, `title`, `detail` — plus `retryAfterSeconds` and the quota state in `rateLimit` when the API reported them. See [errors](/docs/errors) for the codes themselves.

## What is retried

A request the server said was worth sending again — a `429`, a `503`, a dropped connection — is retried with exponential backoff, honouring `Retry-After` when the server sends one. Three retries by default:

```ts
const layer = new Layer({ apiKey, retry: { maxRetries: 5, initialDelayMs: 250, maxDelayMs: 10_000 } })
```

`maxRetries: 0` sends each request exactly once.

A `422` is never retried: no amount of resending fixes a request the API refused on its contents.

## Why a retry is free

Every unsafe request carries an `Idempotency-Key`, minted by the SDK when you supply none. A generation that was accepted but whose response was lost is therefore _replayed_ on the retry rather than run — and paid for — a second time.

Supply your own key when a logical operation spans more than one call into the SDK, so your own retry replays too:

```ts
await layer.inferences.generate({ prompt: '…' }, { idempotencyKey: `nightly-batch-${date}-${index}` })
```

Keys are replayable for 24 hours and scoped to your credential’s owner. Reusing one with a _different_ request is a client bug, and the API says so with `IDEMPOTENCY_KEY_REUSED` rather than returning the wrong result.

## Deprecation notices

The API announces an endpoint it intends to withdraw long before it stops working. Hear about it from your own traffic:

```ts
const layer = new Layer({
  apiKey,
  onDeprecation: (notice) => console.error(`${notice.path} is deprecated, sunset ${notice.sunset}`),
})
```

Each endpoint is announced once per client. See the [deprecation policy](/docs/deprecation) for the timeline a notice commits to.
