---
title: Errors
description: "Every error class the SDK throws, when it fires, and how to handle each one."
---

The SDK throws two error classes. Auth failures are their own class so you can handle them up front; everything else is `BeliefsError` with a structured code.

```ts
import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs'
```

## BetaAccessError

The API key is missing, invalid, or revoked, or your account isn't on the beta allowlist (HTTP 401/403). Surfaces a `signupUrl` for self-service requests.

```ts
try {
  await beliefs.before(input)
} catch (err) {
  if (err instanceof BetaAccessError) {
    console.log(err.signupUrl) // 'https://thinkn.ai/waitlist'
    return redirect(err.signupUrl)
  }
  throw err
}
```

| Property | Type | Notes |
|---|---|---|
| `name` | `string` | `'BetaAccessError'` |
| `message` | `string` | Human-readable reason |
| `status` | `401 \| 403` | HTTP status from the engine |
| `signupUrl` | `string` | Where to request access |

**Not retryable.** Either the key works or it doesn't. Retrying won't change the answer.

## BeliefsError

Everything else. Carries a structured `code`, an HTTP status, and a `retryable` flag. The SDK auto-retries transient errors (429, 5xx) with exponential backoff, so you only see these after retries are exhausted.

```ts
try {
  await beliefs.after(result.text)
} catch (err) {
  if (err instanceof BeliefsError) {
    console.log(err.code)      // 'rate_limit/exceeded'
    console.log(err.status)    // 429
    console.log(err.retryable) // true (already retried, exhausted)
  }
  throw err
}
```

| Property | Type | Notes |
|---|---|---|
| `name` | `string` | `'BeliefsError'` |
| `message` | `string` | Human-readable reason |
| `code` | `string` | Stable, namespaced identifier (see catalog) |
| `status` | `number` | HTTP status from the engine |
| `retryable` | `boolean` | `true` if the SDK retried before throwing |
| `details` | `Record<string, unknown>` | Optional structured context (request id, validation issues, etc.) |

## Code catalog

Error codes are namespaced as `<category>/<reason>`. Categories are stable; reasons may grow.

### `auth/*`

| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| `auth/missing_key` | 401 | No | Constructor was called without `apiKey` or `scopeToken` | Provide one. See [Auth](/dev/sdk/auth). |
| `auth/invalid_key` | 401 | No | Key format is malformed or unknown to the server | Verify the value of `BELIEFS_KEY`. |
| `auth/revoked_key` | 403 | No | Key was valid but revoked (rotated, deleted, or org disabled) | Issue a new key from your profile. |
| `auth/scope_token_expired` | 401 | No | A short-lived `scopeToken` passed its `exp` claim | Mint a fresh token server-side. |

For 401/403 from the beta allowlist specifically, expect [`BetaAccessError`](#betaaccesserror), not `BeliefsError`.

### `validation/*`

| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| `validation/invalid_json` | 400 | No | Request body failed JSON parsing | Check serialization. Usually a non-stringifiable value in `add()` or `after()` payload. |
| `validation/invalid_shape` | 400 | No | Request shape failed engine schema validation | `err.details.issues` lists the Zod issues. |
| `validation/missing_field` | 400 | No | A required field was omitted | `err.details.field` names the field. |

### `rate_limit/*`

| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| `rate_limit/exceeded` | 429 | Yes (auto-retried) | Per-key or per-namespace quota exceeded | Inspect `err.details.retryAfter`. Drop call rate or upgrade. |

### `internal/*`

| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| `internal/error` | 5xx | Yes (auto-retried) | Engine error, retried with backoff before surfacing | If repeated, check status page; file a P1 with `err.details.requestId`. |
| `internal/timeout` | 504 | Yes (auto-retried) | Engine took longer than the configured timeout | Retry, or split into smaller observations. |

### Operation-specific

| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| `remove_where/unsupported_source` | 400 | No | `removeWhere({ source })` was called with a kind other than `block:<id>` | Use `retract()` or `remove()`. Engine support for `agent:`, `thread:`, `source:` is in flight. |

## Retry & backoff

The SDK auto-retries `429` and `5xx` responses with exponential backoff before throwing. By the time you see a `BeliefsError` with `retryable: true`, the SDK has already exhausted its retry budget. You don't need to wrap it in a retry loop yourself.

`retryable: false` errors (auth, validation) are never retried. Don't retry them in your handler. Fix the input.

## Handling pattern

A single try/catch covers both classes. Branch on instance, then on code if you need fine-grained handling.

```ts
import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs'

try {
  const context = await beliefs.before(userMessage)
  // ... agent run, after, etc.
} catch (err) {
  if (err instanceof BetaAccessError) {
    // Send the user to the waitlist
    return { error: 'beta_access_required', signupUrl: err.signupUrl }
  }

  if (err instanceof BeliefsError) {
    if (err.code.startsWith('auth/')) {
      // Auth misconfiguration: log and surface to ops
      console.error('beliefs auth failure', err.code, err.message)
      return { error: 'auth_misconfigured' }
    }

    if (err.code === 'rate_limit/exceeded') {
      // Already auto-retried; back off in your application loop
      return { error: 'rate_limited', retryAfter: err.details?.retryAfter }
    }

    if (err.code.startsWith('validation/')) {
      // Bug in the calling code: log and fix
      console.error('beliefs validation failure', err.details)
      return { error: 'invalid_request' }
    }

    // internal/*: already retried, surface as transient
    return { error: 'beliefs_unavailable' }
  }

  // Not a beliefs error: re-throw for upstream handling
  throw err
}
```

## Where to next

<CardGroup cols={2}>
  <DocsCard title="Auth" description="API keys vs. scope tokens." href="/dev/sdk/auth" />
  <DocsCard title="Core API" description="The full SDK surface." href="/dev/sdk/core-api" />
</CardGroup>
