---
title: Auth
description: "API keys and short-lived scope tokens."
---

The SDK supports two authentication modes for app builders: long-lived `apiKey` (the default) and short-lived `scopeToken` (per-request HS256 JWT, intended for browser and edge runtimes).

## `apiKey`: server-side

Use this from any trusted server runtime: Node, Workers, container backends, agent runtimes. The key is a `bel_live_…` token tied to your account.

```ts
import Beliefs from 'beliefs'

const beliefs = new Beliefs({
  apiKey: process.env.BELIEFS_KEY,
  namespace: 'project-alpha',
  writeScope: 'space',
})
```

The key is sent as a `Bearer` header on every request. Get it from **Profile > API Keys** in the Studio dashboard. See [Install](/dev/start/install) for the full setup walkthrough.

<Callout type="warning" title="Server-side only">
Treat `apiKey` like any account credential: never embed in client bundles, never commit to source control, rotate immediately if leaked. Use `scopeToken` for browser/edge contexts.
</Callout>

## `scopeToken`: browser, edge, untrusted runtimes

When you cannot put an `apiKey` on the device (browsers, edge functions, third-party plugins), mint a short-lived HS256 JWT on your server and hand it to the client. The SDK signs a fresh token from the configured claims on every request, so you never need to refresh tokens manually.

```ts
import Beliefs from 'beliefs'

const beliefs = new Beliefs({
  scopeToken: {
    secret: process.env.BELIEFS_SCOPE_TOKEN_SECRET!,
    claims: {
      scopeType: 'space',
      scopeId: currentWorkspace.id,
      actorUserId: currentUser.id,    // optional: who is acting
      sessionId: currentSession.id,   // optional: for session pinning
    },
  },
  namespace: 'project-alpha',
})

await beliefs.before('What did the user just say?')
```

Claims:

| Field | Required | What it does |
|-------|----------|--------------|
| `scopeType` | yes | The scope kind: `'space'`, `'studio'`, `'org'`, or `'user'`. |
| `scopeId` | yes | The id of that scope (e.g. workspace id when `scopeType: 'space'`). |
| `actorUserId` | no | Who is acting. Pass when you want every change attributed to a specific user. |
| `sessionId` | no | Pin to a session for analytics and cross-session isolation. |
| `visibleSpaceIds` | no | Array of additional space ids the actor can read across. |
| `exp` | no | Per-token expiry override (Unix seconds). The SDK applies a sensible default if omitted. |

Two optional fields on the outer `scopeToken` config tune token lifetime:

- **`audience`**: restricts the minted token to a specific API endpoint. The engine rejects the token if the audience doesn't match. Use this to narrow tokens by deployment.
- **`ttlSeconds`**: overrides the default token lifetime (the engine sets a sensible default if omitted). Shorten this for high-sensitivity sessions; lengthen if you're seeing churn from repeated mints.

How it works:

1. Your server provisions a `secret` (32+ bytes of random) and stores it alongside any per-user/per-session claims.
2. The SDK accepts the secret and claims at construction time.
3. On each request, the SDK mints a fresh HS256 JWT from those claims and sends it as a `Bearer` token.
4. The engine verifies the signature against the shared secret and applies the claims as the request's scope.

The secret only stays safe when the SDK client is constructed on your server. If you instantiate the client in a browser, the secret is embedded in the bundle and effectively public. Provision a session-scoped secret you can revoke on logout, or proxy SDK calls through a server endpoint that holds the long-lived secret.

**Mode switching is automatic.** When you provide `scopeToken`, the SDK ignores any `apiKey` for that instance.

## When to use which

| Runtime | Mode | Why |
|---------|------|-----|
| Node server, agent worker, container | `apiKey` | Long-lived credential, simplest. |
| Next.js Route Handler, Cloudflare Worker, Vercel Function | `apiKey` | Same as above; the runtime is trusted. |
| Browser (React, Vue, Svelte) | `scopeToken` | Avoids embedding a long-lived account credential. |
| Edge functions invoked by an untrusted client | `scopeToken` | Per-request scope narrowing via claims. |
| Third-party plugin / extension | `scopeToken` | Scope and revoke per session. |

## Errors

`BetaAccessError` (HTTP 401/403): key missing, invalid, or revoked. The SDK surfaces a `signupUrl` for self-service requests:

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

try {
  await beliefs.before(input)
} catch (err) {
  if (err instanceof BetaAccessError) {
    console.log(err.signupUrl)
  }
}
```

`BeliefsError` with code `auth/missing_key`: the SDK was constructed without any auth at all.

<Callout type="info" title="What's not covered here">
The SDK has a third internal mode (`serviceToken`) used by Studio's BFF. It is not part of the public app-builder surface and should not be used by external integrators.
</Callout>
