Handing out keys
A Brain key reaches exactly one Brain with exactly the capabilities you grant. It is what you hand to anything you do not run yourself.
curl -X POST "$API/v1/brains/tenant:acme/keys" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "name": "Acme web app", "capabilities": ["read"] }'{ "data": { "key": { "id": "key_7f3a1c", "name": "Acme web app", "capabilities": ["read"], "status": "active", "createdAt": "2026-08-14T09:41:02Z" }, "secret": "nbk_9f2c…" }}secret is shown once. Nicia stores only its hash. Store it now; a lost
secret is replaced by rotating, never recovered.
Three shapes that cover most cases
Section titled “Three shapes that cover most cases”An app reading one customer’s Brain
Section titled “An app reading one customer’s Brain”{ "name": "acme dashboard", "capabilities": ["read"] }Read-only, one tenant. Safe in a server-rendered app. Never ship it to a browser — see End users below.
A pipeline writing into one Brain
Section titled “A pipeline writing into one Brain”{ "name": "salesforce sync", "capabilities": ["read", "write"] }A key says what this writer may reach, never how its writes are admitted.
Whether a write lands or stages is the Brain’s mode, and the Brain’s admission
policy decides it for every write, whoever sent it. See
Review.
A department that owns its Brain
Section titled “A department that owns its Brain”{ "name": "finance", "capabilities": ["read", "write", "review", "admin"], "expiresAt": "2027-01-01T00:00:00Z"}Corporate IT provisions a Brain per department with a management key, then hands
each department a key that governs their own Brain and nothing else. admin is
the review switch — PATCH /v1/brains/{handle}, which turns review on or off —
but not the ability to read the Brain’s contents, which is a separate capability
on purpose. That separation is only worth anything if it holds for bulk egress
too, so admin does not carry export or webhook control either; neither is on
the Brain-key surface today. The department above reads because it also holds
read. Creating and deleting a Brain stay with IT’s management key; no Brain key
reaches them. See Capabilities.
Idempotent minting
Section titled “Idempotent minting”Derive the idempotency key from something stable on your side and a retried provisioning run returns the same key instead of minting a second live secret:
curl -X POST "$API/v1/brains/tenant:acme/keys" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "name": "acme app", "capabilities": ["read"], "idempotencyKey": "app-key:acme" }'A replay returns the original key record with "secret": null — the secret is
still only ever shown at the moment of creation.
Listing, rotating, revoking
Section titled “Listing, rotating, revoking”curl "$API/v1/brains/tenant:acme/keys" \ -H "authorization: Bearer $NICIA_KEY"{ "data": { "keys": [ { "id": "key_7f3a1c", "name": "Acme web app", "capabilities": ["read"], "lastUsedAt": "2026-08-14T11:58:03Z", "status": "active", "createdAt": "2026-08-14T09:41:02Z" } ] }}lastUsedAt is how you find the keys nobody is using any more.
# Rotate: new secret now, old one keeps working for the overlap windowcurl -X POST "$API/v1/brains/tenant:acme/keys/key_7f3a1c/rotate" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "overlapSeconds": 3600 }'
# Revoke: immediatecurl -X DELETE "$API/v1/brains/tenant:acme/keys/key_7f3a1c" \ -H "authorization: Bearer $NICIA_KEY"Rotation exists so a running fleet does not drop requests while it redeploys. Revocation takes effect on the next request, everywhere.
End users
Section titled “End users”Never put a Brain key in a browser, a mobile app, or anything else a user can open. Two safe patterns:
Proxy through your backend. Your server holds the key, authenticates your
user, and calls Nicia on their behalf. Pass a consumerId so you can later
reconstruct exactly what one user was shown:
const { data } = await nicia.context(brainKey, { prompt: question, consumerId: `user:${session.userId}`,});consumerId is a correlation label you choose. It is never authorization — it
does not restrict what the call can reach, so the scoping still has to come from
the key.
Mint a short-lived key per session. When a user genuinely needs to call Nicia directly:
-d '{ "name": "session u_4471", "capabilities": ["read"], "expiresAt": "2026-08-14T13:00:00Z" }'Expired keys are 401 and cannot be revived.
Attribution
Section titled “Attribution”Everything a key does is attributed to it, derived server-side from the credential rather than from anything the caller sends. Staged changes name the key that proposed them, receipts name the key that made the read, and revoking a key leaves that history intact.
{ "data": { "author": { "kind": "key", "id": "key_7f3a1c", "name": "support-agent" } }}This is why a key per workload beats a shared key: it is the difference between “an agent wrote this” and “the support triage agent wrote this on 14 August.”
- Authentication — the two key types side by side.
- Receipts — proving what a key was shown.