Authentication
There is one HTTP scheme: authorization: Bearer. What the credential reaches
depends on its prefix, not the header it travels in.
| Key | Reaches | Held by |
|---|---|---|
Management key (nsk_) |
Every Brain in one organization, plus creating Brains and minting Brain keys | Your backend |
Brain key (nbk_) |
One Brain, with only the capabilities you granted | An app, a department, an end user, an agent |
A Brain key is bound to one Brain, so it never needs to be told which one. A management key is bound to one organization the moment it is minted, so it doesn’t either.
Management keys
Section titled “Management keys”Sign in at app.nicia.ai, open Settings → API keys, and mint one under Organization keys. The secret is shown once and stored only as a hash — copy it now; a lost key is replaced, never recovered.
curl https://api.nicia.ai/v1/brains \ -H "authorization: Bearer $NICIA_KEY"That’s it — no second id to send. The key is bound to the organization it was minted from, so every call it makes reaches that organization and no other.
Minting one over HTTP
Section titled “Minting one over HTTP”POST /v1/keys mints a key, GET /v1/keys lists them, and
DELETE /v1/keys/{id} revokes one — all three from a signed-in browser
session, never from a key, in either header: a valid management key is
refused with 403 forbidden here, because a credential that can mint its own
successor would outlive its own revocation. That is why the console above is the
first step, and this is the automation path afterwards.
Account creation is open — no invitation code — and a script can do it. Two things decide whether it works, and both are easy to get backwards:
Send an Origin header — and send the app’s own origin. This is the
opposite of what the shape of the thing suggests, so it is worth being precise
about.
Origin validation is CSRF protection and engages for anything that looks
browser-originated. Four things trigger it, not three: a cookie, an Origin,
a Referer — and any Sec-Fetch-Site, Sec-Fetch-Mode or Sec-Fetch-Dest
header. That fourth one is the trap, because you do not write it. Node’s
built-in fetch sends Sec-Fetch-Mode: cors on every request, including a
plain server-to-server POST, so validation engages, finds no origin, and answers
403 MISSING_OR_NULL_ORIGIN.
curl sends none of those headers, which is why the same call succeeds from a
shell and fails from a script. If you are debugging this, that difference is
the whole story.
So send the app’s own origin on the sign-up call, and it validates against the trusted list and passes. In production that is one header:
origin: https://app.nicia.aiSetting Origin to some other host does not help — validation is against the
trusted list, not against whether the header is present.
Use the cookie sign-up already returned. The sign-up endpoint under
/api/auth answers with a session, so a script never needs to call sign-in —
sign-in is the one that requires a verified email address. Signing up and then
signing in is the long way round, and it is the way that fails. (Those
endpoints belong to the auth layer rather than to this API’s contracts, so they
are not in the OpenAPI reference.)
An account created this way starts unverified, which caps what it may consume until the address is confirmed. See Billing for the limits.
curl -X POST https://api.nicia.ai/v1/keys \ -H "cookie: $NICIA_SESSION" \ -H "x-organization-id: $NICIA_ORG" \ -H "content-type: application/json" \ -d '{ "name": "provisioning service" }'{ "data": { "key": { "id": "key_2d90ff", "name": "provisioning service", "organizationId": "org_4471", "status": "active", "createdAt": "2026-08-14T09:15:41Z" }, "secret": "nsk_…" }}The secret is returned exactly once. expiresAt, if you send one, must be
between 1 and 365 days out. Every call mints a new key — this endpoint is not
idempotent.
A management key authorizes only while the person who minted it is still a member of that organization: remove them and their keys stop on the next request, everywhere. Mint from an account the organization keeps if the credential has to outlive any one employee, and revoke explicitly rather than relying on an offboarding checklist.
curl https://api.nicia.ai/v1/keys \ -H "cookie: $NICIA_SESSION" \ -H "x-organization-id: $NICIA_ORG"
curl -X DELETE https://api.nicia.ai/v1/keys/key_2d90ff \ -H "cookie: $NICIA_SESSION" \ -H "x-organization-id: $NICIA_ORG"Listing includes revoked keys, so the audit trail survives, and never returns a secret. Revoking is idempotent, and takes effect on the next request everywhere. It disables the row rather than deleting it, so it stays listed — the console’s confirm dialog says so before you click through.
Use a management key when your own backend is the only thing holding it: a provisioning service, a nightly sync job, a server-side agent that works across many customers. It is the right key for the Quickstart because there is nothing to set up before you can use it.
Brain keys
Section titled “Brain keys”A Brain key reaches exactly one Brain, with exactly the capabilities you list:
curl -X POST https://api.nicia.ai/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:15:41Z" }, "secret": "nbk_…" }}Use one whenever the holder is not you: a customer-facing app, an end user’s Claude Code, an agent you did not write, a department inside a company whose IT team provisioned the Brain. A leaked Brain key reaches one customer’s Brain with one capability set, and revoking it touches nothing else. Minting, rotating, and revoking a Brain key are management-key operations — the Brain key itself is what you hand to the holder.
Capabilities
Section titled “Capabilities”| Capability | Grants |
|---|---|
read |
/context, /search, /query, reading records and receipts, and the Brain’s own name, purpose, and mode |
write |
/documents, record writes, /batch |
review |
Listing and settling changes |
admin |
PATCH /v1/brains/{handle} — the review switch — but not reading the Brain’s contents |
admin deliberately grants no access to knowledge: administering a Brain is not
a reason to read what it knows. It is the capability that turns review on and
off, so a service that flips Brains between open and reviewed needs an
admin key and nothing more.
That separation is only worth anything if it holds for bulk egress too, so
admin carries neither export nor webhook control: an admin key cannot start
a bulk export or point a webhook at change payloads. Neither has a v2 endpoint
at all — both are reachable only through the older id-addressed paths — and
when they arrive they get their own capability rather than being folded into
admin, and the changelog will say so.
Schema evolution goes the other way: evolving a schema and promoting emerged
structure are served on admin, beside the review switch. Both produce a
governed Review proposal; acceptance applies the canonical schema mutation and
settles only after declared-vocabulary materialization verifies ready. Today,
only exact slug equality is queryable; every non-slug predicate returns
400 index_not_ready until property indexes ship. The
API reference is the list of what each capability reaches.
Whether writes land or stage is not a capability at all: it is the Brain’s
mode, set on the Brain itself.
Five things are management-key only, and no Brain key grants them, at any
capability: creating a Brain (PUT /v1/brains/{handle}), deleting one, listing
the organization’s Brains, minting/rotating/revoking/listing a Brain’s keys, and
reading a Brain’s schema lineage (GET /v1/brains/{handle}/schema-lineage). The
first four are refused because a key that could mint its own successor would
make revocation meaningless, and a Brain key cannot exist before its Brain does.
Schema lineage is refused for a different reason: it reports which
organization schema a Brain forked from and whether that baseline has moved —
a fact about the organization’s schema registry, outside what a single-Brain
credential is scoped to. All five answer 401 unauthorized to a Brain key, not
403.
There is no preset parameter on minting a Brain key — send the
capabilities array you want. The common shapes are ["read"],
["read", "write"], and ["read", "review"].
Rotating and revoking
Section titled “Rotating and revoking”# Mint a replacement; the old secret keeps working for 60 more secondscurl -X POST https://api.nicia.ai/v1/brains/tenant:acme/keys/key_7f3a1c/rotate \ -H "authorization: Bearer $NICIA_KEY"
curl -X DELETE https://api.nicia.ai/v1/brains/tenant:acme/keys/key_7f3a1c \ -H "authorization: Bearer $NICIA_KEY"Rotation’s overlap window is 60 seconds, not “until you revoke”: deploy the new secret inside it or the fleet still holding the old one starts failing on its own. Revocation is separate, and takes effect on the next request everywhere — it is the way to end a compromised key now rather than in a minute.
Which key should I use?
Section titled “Which key should I use?”- Provisioning a Brain per customer from your backend → management key.
- Your server-side agent reading many customers’ Brains → management key.
- Shipping a key inside an app, a browser, or an end user’s tool → Brain key.
- Corporate IT creating a Brain per department and handing it over → Brain key per department.
If a key would ever leave a machine you control, it should be a Brain key.
Errors
Section titled “Errors”A missing or invalid key is 401 unauthorized. A valid key without the required
capability is 403 forbidden, and the message names both the capability the
operation needed and the ones your key holds. Too many requests is
429 rate_limited with a Retry-After header. See Errors.
- Handing out keys — patterns for fleets and end users.
- Quickstart — a key to an answer in three calls.