Skip to content
Beta. This surface may change before GA; breaking changes are dated in the changelog.

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.

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.

Terminal window
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.

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

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

Terminal window
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.

Terminal window
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.

A Brain key reaches exactly one Brain, with exactly the capabilities you list:

Terminal window
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.

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"].

Terminal window
# Mint a replacement; the old secret keeps working for 60 more seconds
curl -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.

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

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.