Brain

Authentication

Unison auth in one page: email-OTP provisioning, API key scopes (brain:read, brain:write, brain:admin, brain:act-as), key lifecycle, headless machine auth, and actor delegation.

unison-brain

Every request carries Authorization: Bearer <token> where the token is an API key (usk_...). Only the provisioning endpoints are unauthenticated. The server is the only security boundary - clients never pre-check scopes.

Getting a key

Interactive (email OTP, no browser): unison auth login - enter your email; a new account gets a key immediately (unverified, usage-capped) plus an OTP that lifts the caps; an existing account gets a recovery OTP that mints a new key.

Raw HTTP:

EndpointBodyReturns
POST /v1/auth/provision{ email }{ apiKey, workspaceId, status, emailSent } - 409 email_registered if the email exists
POST /v1/auth/verify{ email, code }first time: verifies; repeat: mints a new key
POST /v1/auth/request-key{ email }recovery OTP (response never leaks whether the email is registered)

CI / machines: export UNISON_TOKEN=usk_... - no interaction. Mint dedicated keys with unison auth keys create.

Scopes

ScopeUnlocks
brain:readall GETs, key management, invitations
brain:writedocument write/delete/tag/share, entity upsert, fact record/correct/invalidate, link
brain:admindedup review (merge/unmerge), job retry
brain:act-asactor delegation (below); only workspace owners/admins can mint keys carrying it

A token lacking the required scope gets 403 forbidden. New keys may only request a subset of the minting caller's scopes.

Key management (brain:read)

  • GET /v1/auth/keys - list (never returns hashes)
  • POST /v1/auth/keys - body { name?, scopes?, workspaceId? }; token returned once
  • DELETE /v1/auth/keys/:id - revoke
  • GET /v1/auth/whoami - identity, workspace, scopes, and actedAs when delegation is active

Acting on behalf of end users

One service key serves many end users without separate accounts - the pattern mem0/Zep users will recognize:

const { token } = await client.keys.create({
  name: "my-service",
  scopes: ["brain:read", "brain:write", "brain:act-as"],
});

const u1 = serviceClient.withActor("user-001");
await u1.write({ path: "/private/notes/chat.md", bodyMd: "user said …" });

const u2 = serviceClient.withActor("user-002");
await u2.search("what did I say?"); // isolated from user-001

Under the hood this sends X-Unison-Actor: <externalId> on each request; shadow users are auto-created and each actor gets an isolated /private/ namespace. CLI: --actor <id> or UNISON_ACTOR=<id>.

Invitations and workspaces

POST /v1/auth/invitations ({ email, role? }, roles admin|member|viewer) invites a teammate into your workspace; GET /v1/auth/workspaces lists your memberships. Provisioning with an invited email joins the inviting workspace automatically.

On this page