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.
1import Beliefs from 'beliefs'
2
3const beliefs = new Beliefs({
4 apiKey: process.env.BELIEFS_KEY,
5 namespace: 'project-alpha',
6 writeScope: 'space',
7})The key is sent as a Bearer header on every request. Get it from Profile > API Keys in the Studio dashboard. See Install for the full setup walkthrough.
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.
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.
1import Beliefs from 'beliefs'
2
3const beliefs = new Beliefs({
4 scopeToken: {
5 secret: process.env.BELIEFS_SCOPE_TOKEN_SECRET!,
6 claims: {
7 scopeType: 'space',
8 scopeId: currentWorkspace.id,
9 actorUserId: currentUser.id, // optional — who is acting
10 sessionId: currentSession.id, // optional — for session pinning
11 },
12 },
13 namespace: 'project-alpha',
14})
15
16await beliefs.before('What did the user just say?')Claims:
| Field | Required | What it does |
|---|---|---|
scopeType | yes | 'space', 'studio', 'org', or 'user' — the scope kind. |
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. |
Optional audience and ttlSeconds on the outer scopeToken config let you scope tokens to a specific audience and override the default TTL.
How it works:
- Your server provisions a
secret(32+ bytes of random) and stores it alongside any per-user/per-session claims. - The SDK accepts the secret and claims at construction time.
- On each request, the SDK mints a fresh HS256 JWT from those claims and sends it as a
Bearertoken. - The engine verifies the signature against the shared secret and applies the claims as the request's scope.
The secret never leaves your server only when you build the client there. If you build the client in a browser, the secret is embedded — provision a session-scoped secret and revoke it on logout, or proxy through your server.
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:
1import Beliefs, { BetaAccessError } from 'beliefs'
2
3try {
4 await beliefs.before(input)
5} catch (err) {
6 if (err instanceof BetaAccessError) {
7 console.log(err.signupUrl)
8 }
9}BeliefsError with code auth/missing_key — the SDK was constructed without any auth at all.
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.