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 | 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:
- 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 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:
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.