> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.livingbrain.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.livingbrain.com/_mcp/server.

# Errors

## The envelope

Every error shares one shape:

```json
{
  "statusCode": 401,
  "timestamp": "2026-08-27T18:57:27.998Z",
  "path": "/v1/brains",
  "message": "Invalid API key",
  "body": {
    "message": "Invalid API key",
    "error": "Unauthorized",
    "statusCode": 401
  }
}
```

`body` carries the original framework response when available — for validation failures it
holds per-field messages, which is usually what you want to surface.

## Typed errors

Both SDKs raise a typed error per status, so you can branch without string-matching.

**`TypeScript`**

```typescript TypeScript
import { LivingBrain, LivingBrainError } from "@livingbrain/sdk";

try {
  await client.workspace.getPage({ brainId, slug });
} catch (err) {
  if (err instanceof LivingBrain.NotFoundError) {
    // no such page in this brain
  } else if (err instanceof LivingBrain.TooManyRequestsError) {
    // back off and retry
  } else if (err instanceof LivingBrainError) {
    console.error(err.statusCode, err.message, err.body);
  }
}
```

**`Python`**

```python Python
from livingbrain.core.api_error import ApiError
from livingbrain.errors import NotFoundError, TooManyRequestsError

try:
    client.workspace.get_page(brain_id=brain_id, slug=slug)
except NotFoundError:
    ...
except TooManyRequestsError:
    ...
except ApiError as err:
    print(err.status_code, err.body)
```

## Status codes

| Code  | Meaning                               | What to do                                                                |
| ----- | ------------------------------------- | ------------------------------------------------------------------------- |
| `400` | Malformed request                     | Read `body` for per-field detail. Don't retry unchanged.                  |
| `401` | `Missing API key` / `Invalid API key` | Check the `x-api-key` header. See [Authentication](/docs/authentication). |
| `404` | Not found — or not yours              | Confirm the `x-subject-id` owns this brain.                               |
| `429` | Rate limited                          | Back off and retry; the SDKs do this for you.                             |
| `5xx` | Server error                          | Retried automatically.                                                    |

> **Info**
>
> A `404` is also what you get for a brain that exists but belongs to a different subject —
> the API doesn't distinguish, so it can't be used to probe for other users' brains.

## Retries

Both SDKs retry `429` and `5xx` automatically with exponential backoff. Tune per call:

**`TypeScript`**

```typescript TypeScript
await client.brains.list({ maxRetries: 5, timeoutInSeconds: 30 });
```

**`Python`**

```python Python
from livingbrain.core.request_options import RequestOptions

client.brains.list(request_options=RequestOptions(max_retries=5, timeout_in_seconds=30))
```

`400`, `401`, and `404` are not retried — they won't succeed on a second attempt.