thinkn
  • Product
    Manifesto
    The reason we exist
    Founder Studioprivate beta
    Make better product decisions faster
    Belief SDKinvite only
    Add belief states to your AI system
    Request Access →Join the private beta waitlist
  • Docs
  • Pricing
  • FAQ
  • Docs
  • Pricing
  • FAQ
Sign In
Welcome
  • Start Here
  • Introduction
  • Install
  • Quickstart
  • Hack Guide
  • FAQ
  • 1. The Problem
  • 2. Memory vs Beliefs
  • 3. Drift
  • 4. Worked Examples
  • Overview
  • Core API
  • Reads
  • Moves
  • Forecast
  • Tools
  • Trust
  • Streaming
  • Auth
  • Scoping
  • Loop Patterns
  • Patterns
sdk/auth.mdx

Auth

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.

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:

FieldRequiredWhat it does
scopeTypeyes'space', 'studio', 'org', or 'user' — the scope kind.
scopeIdyesThe id of that scope (e.g. workspace id when scopeType: 'space').
actorUserIdnoWho is acting. Pass when you want every change attributed to a specific user.
sessionIdnoPin to a session for analytics and cross-session isolation.
visibleSpaceIdsnoArray of additional space ids the actor can read across.
expnoPer-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:

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

RuntimeModeWhy
Node server, agent worker, containerapiKeyLong-lived credential, simplest.
Next.js Route Handler, Cloudflare Worker, Vercel FunctionapiKeySame as above; the runtime is trusted.
Browser (React, Vue, Svelte)scopeTokenAvoids embedding a long-lived account credential.
Edge functions invoked by an untrusted clientscopeTokenPer-request scope narrowing via claims.
Third-party plugin / extensionscopeTokenScope 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.

PreviousStreaming
NextScoping

On this page

  • apiKey — server-side
  • scopeToken — browser, edge, untrusted runtimes
  • When to use which
  • Errors