Identity walkthrough
How an API key carries identity — it binds a (namespace, agent) scope at creation, the server derives everything from it, and you confirm it with whoami.
- Access to a Brain deployment (managed cloud or self-hosted).
- An API key, provisioned from the Arc Labs dashboard.
- Familiarity with Authentication.
A client that authenticates with a key, prints the (namespace, agent, permissions) the server derives from it, and — for backends serving many tenants — runs an operation as a different identity with act_as.
The model in one paragraph
An API key is durably bound at creation to a namespace (the company / tenant — the top-level isolation boundary), an agent (the app / persona within the tenant), and a set of permission flags. The server resolves (namespace, agent, permissions) from the key on every request and scopes all reads and writes to it. Clients never send a scope on the wire — there is no namespace or agent header to set, and a key can only ever touch data in its own namespace. This is identity-by-key: it removes the whole class of bugs where client and server disagree about who a request is for.
Step-by-step
Send the key as Authorization: Bearer <key> or X-API-Key: <key> — the two are interchangeable. The SDK takes the key once at construction.
import { BrainHttpClient } from "@brain-db/sdk";
const brain = new BrainHttpClient({
apiKey: process.env.BRAIN_API_KEY!,
baseUrl: "https://api.arc-labs.ai",
});import os
from brain_db_sdk import BrainHttpClient
brain = BrainHttpClient(os.environ["BRAIN_API_KEY"], base_url="https://api.arc-labs.ai")use brain_db_sdk::BrainHttpClient;
// base_url FIRST, then the API key.
let brain = BrainHttpClient::new("https://api.arc-labs.ai", &std::env::var("BRAIN_API_KEY")?);curl https://api.arc-labs.ai/v1/whoami \
-H "Authorization: Bearer $BRAIN_API_KEY"
# or: -H "X-API-Key: $BRAIN_API_KEY"whoami returns exactly what the server derived from the key. Use it as a startup sanity check — fail fast if the key resolves to the wrong namespace.
const me = await brain.whoami();
console.log(me.namespace, me.agent_id);
console.log(me.permissions); // { can_encode, can_recall, can_forget, can_plan, can_reason, can_admin }me = brain.whoami()
print(me.namespace, me.agent_id)
print(me.permissions)let me = brain.whoami().await?;
println!("{:?} {}", me.namespace, me.agent_id);
// permissions: can_encode, can_recall, can_forget, can_plan, can_reason, can_admin
println!("{:?}", me.permissions);A key without the flag a verb requires is rejected with 403 forbidden before the handler runs — e.g. a read-only key calling encode.
Key lifecycle is a control-plane concern handled in the Arc Labs dashboard — the data-plane /v1/* API never mints, rotates, or revokes a key.
- Create — from the target agent. The plaintext secret is shown once; store it immediately.
- Rotate — mints a replacement bound to the same
(namespace, agent, permissions)and retires the old secret in one step, so there is never a window with no valid key. - Revoke — takes effect immediately; subsequent requests with the key return
401 unauthorized.
Prefer rotate over revoke-then-create. See API keys.
A backend that serves many tenants from one process can hold a single service key and run each request under a different effective (namespace, agent) with act_as. This is wire-only and requires the key to hold the can_act_as grant; otherwise the server rejects with ActAsDenied.
import { EncodeBuilder } from "@brain-db/sdk";
await client.encode(
new EncodeBuilder("Tenant-scoped memory.").actAs(tenantNamespace, tenantAgentId).build(),
);from brain_db_sdk import EncodeBuilder
client.encode(
EncodeBuilder("Tenant-scoped memory.").act_as(tenant_namespace, tenant_agent_id).build()
)use brain_db_sdk::EncodeBuilder;
// act_as rebinds the effective (namespace, agent) for this one write; the
// connection principal must hold the can_act_as grant. tenant_namespace is a
// &str/String; tenant_agent_id is the target agent's WireUuid ([u8; 16]).
client
.encode(
&EncodeBuilder::new("Tenant-scoped memory.")
.act_as(tenant_namespace, tenant_agent_id)
.build(),
)
.await?;Permissions
The resolved identity carries per-verb flags, fixed at key creation: can_encode, can_recall, can_forget, can_plan, can_reason, can_admin. To change what a key may do, provision a new key with the flags you want and rotate. See Authentication → Permissions.
Give each app (agent) its own key, and rotate on a schedule and on every personnel change. Because the key is the isolation boundary, sharing one across apps means you cannot rotate or revoke one without disrupting the others.
Was this page helpful?
Retries and cancellation
Configure the HTTP client's built-in retry policy, retry wire calls safely with the request-id contract, and bound calls with timeouts — the SDKs take no external AbortSignal.
Multi-tenant patterns
Serve many tenants correctly — namespace-per-tenant isolation, one key per agent, and a pooled service key with per-request act_as. No user axis, no scope juggling.