Quickstart
Agents don’t need memory. They need a Brain. This quickstart gets you a cited answer in three HTTP calls: create a Brain, put something in it, ask it a question. No schema to design, no policy to configure, no ids to write down.
1. Get a key
Section titled “1. Get a key”Sign in at app.nicia.ai, open Settings → API keys, and mint an organization key under “Organization keys”. The secret is shown once — copy it now.
Getting this first key is interactive, and nothing on this site shortcuts it. Creating an account is open to anyone, but signing in needs a verified email address, and both endpoints refuse a request that did not come from the product frontend. Budget for one person in a browser, once — every call below is automatable afterwards. See Authentication.
export NICIA_KEY="…"export API="https://api.nicia.ai"An organization key can do anything inside the organization you minted it from:
create Brains, read and write them, and mint narrower keys to hand out later.
That is why it is the right key for a quickstart and the wrong key to ship
inside a client app. It is bound to that organization at mint time, so every
call below sends it alone, in authorization: Bearer — there is no second id
to copy or send. See Authentication.
Every response is { "data": … } on success and
{ "error": { "kind", "message" } } on failure. See Errors.
2. Create a Brain
Section titled “2. Create a Brain”A Brain is addressed by a handle you choose: a customer id, an employee id, a project slug — anything stable that you already have. The handle is public and appears in every URL. A key is always a secret credential; the two are never the same thing.
curl -X PUT "$API/v1/brains/tenant:acme" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "name": "Acme", "purpose": "Everything we know about Acme." }'{ "data": { "brain": { "id": "brain_3a91c7", "generation": "6f1e4d6b-9f0c-4a21-8f3e-1f1a2b3c4d5e", "handle": "tenant:acme", "name": "Acme", "purpose": "Everything we know about Acme.", "schema": "nicia-base", "mode": "open", "createdAt": "2026-08-14T09:12:04.221Z", "updatedAt": "2026-08-14T09:12:04.221Z" } }}PUT is create-or-update on your handle, so it is safe to call on every
request, in every worker, after every crash. The first call returns 201, every
identical call after it returns 200, and there is no “does it exist yet” query
to race against.
Three defaults worth knowing:
schema: "nicia-base"— a deterministic starter vocabulary (people, companies, deals, concepts, notes). You did not have to design anything.nicia-core, the domain-neutral vocabulary, is available by naming it explicitly on the create call — the initial schema is fixed on creation, and later changes go through governed schema evolution; see Schemas).mode: "open"— most writes land immediately; destructive changes still wait for approval. Switch to"reviewed"when you want a human to approve what agents write. See Review.- The Brain is addressed by
tenant:acmefrom here on. Nicia’s ownidcomes back in the response and works in the same slot, but nothing on this site asks for it — every path below takes the handle. Store it only if you call one of the older id-addressed paths.generationbeside it is the Brain’s lifecycle fence — opaque, re-minted by a revive or reprovision, and needed only by the generation-scoped writes on the older planes.
3. Put something in
Section titled “3. Put something in”Documents and structured records are peer on-ramps. Use whichever you have.
# A document — text, markdown, a transcript. Text, not bytes: convert files first.curl -X POST "$API/v1/brains/tenant:acme/documents" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "id": "hubspot:deal:412", "text": "Renewal call 2026-03-14. Acme renewed at $120k for 24 months. Bob Smith (Head of Platform) pushed for SSO before Q3.", "metadata": { "source": "hubspot", "type": "call-note" } }'{ "data": { "document": { "id": "hubspot:deal:412", "status": "queued" } }}id is yours. Sending the same id again replaces that document, so a nightly
sync is a loop of POSTs and nothing else.
If what you have is already structured, send it as a record instead — same Brain, no schema step:
curl -X PATCH "$API/v1/brains/tenant:acme/records/bob-smith" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "fields": { "name": "Bob Smith", "role": "Head of Platform" } }'4. Ask it something
Section titled “4. Ask it something”curl -X POST "$API/v1/brains/tenant:acme/context" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "prompt": "What should I know before the renewal call?", "maxTokens": 2000 }'{ "data": { "text": "Renewal call 2026-03-14. Acme renewed at $120k for 24 months. Bob Smith (Head of Platform) pushed for SSO before Q3.\n\nbob-smith\nname: Bob Smith\nrole: Head of Platform", "citations": [ { "source": "hubspot:deal:412", "slug": "brain-evidence-0dd3e2449851d9ba41924c42bd7a61375a8b78050bfe2ca7192eb918d626236a", "quote": "Renewal call 2026-03-14. Acme renewed at $120k for 24 months. Bob Smith (Head of Platform) pushed for SSO before Q3.", "recordedAt": "2026-08-14T09:13:10.882Z" }, { "slug": "bob-smith", "quote": "bob-smith\nname: Bob Smith\nrole: Head of Platform", "recordedAt": "2026-08-14T09:13:44.117Z" } ], "staged": 0, "warnings": [], "receipt": "rcp_019ffefe", "truncated": false }}Paste text straight into your prompt. Show citations to your user. Keep
receipt in your logs — it is how you reconstruct months later exactly what the
model was shown. See Receipts.
text is one block per source, blank-line separated: a document contributes its
prose — plus a name line and its frontmatter keys, when it carries any — and a
record its label, its kind, and its values. A record with no label is headed by
its id, which is why Bob’s block starts bob-smith. Each block is the matching
citations[].quote, verbatim — so text is never more than the sum of what you
can show a user, and never less. See
what text is.
staged is the count of changes waiting for review. It is usually 0 on an
open Brain, whose writes are admitted as they arrive; a non-zero value tells
you an empty answer means “waiting”, not “nothing there”. See
Context.
That is the cited read. The next ten minutes are the loop that makes it
defensible: correct what was wrong, see the receipt go stale, name who was
told, and notice when two sources disagree — on /context, not on a second
endpoint.
5. Correct, see who was told, notice disagreement
Section titled “5. Correct, see who was told, notice disagreement”The receipt from step 4 is rcp_019ffefe. Rewrite the document — same id,
new text — and the next read is current. The old receipt is not.
curl -X POST "$API/v1/brains/tenant:acme/documents" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "id": "hubspot:deal:412", "text": "Amendment signed 2026-08-01. Acme renewed at $145k for 24 months. Bob Smith (Head of Platform) pushed for SSO before Q3." }'curl "$API/v1/brains/tenant:acme/receipts/rcp_019ffefe/now" \ -H "authorization: Bearer $NICIA_KEY"{ "data": { "changed": [ { "source": "hubspot:deal:412", "shownVersion": 2, "currentVersion": 3, "status": "superseded" } ], "unchanged": 1, "deleted": 0 }}$120k was right when it was said. It is wrong now. eventId is the change
that was superseded — the original write, named in History — not the
correction:
curl "$API/v1/brains/tenant:acme/corrections/chg_44a1e0/affected-reads" \ -H "authorization: Bearer $NICIA_KEY"{ "data": { "observedConsumers": [ { "connectionId": "conn_7f3a1c", "consumerId": "user:4471", "receiptId": "rcp_019ffefe", "servedAt": "2026-08-14T11:02:44.000Z" } ], "candidateRuns": [], "truncated": false }}That is who was told: every receipted read of the superseded content.
observedConsumers is receipt-backed and never claimed exhaustive;
candidateRuns is the weaker “the Brain was available” list and is never
unioned in. The knowledge.corrected webhook carries the same
observedConsumers list. See Receipts.
A second source can disagree without anyone correcting anything. Write two
warranted facts about the same subject and predicate — each with its own
source — and /context names the disagreement inline. There is no
conflicts[] field; the signal is warnings[] kind contested plus
per-item status.
source on a statement is a record id — kebab, same rule as every other
record — not a document id with colons. Create the source records first:
curl -X PATCH "$API/v1/brains/tenant:acme/records/hubspot-deal-412" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "fields": { "name": "HubSpot deal 412" } }'
curl -X PATCH "$API/v1/brains/tenant:acme/records/netsuite-renewal" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "fields": { "name": "NetSuite renewal record" } }'
curl -X PATCH "$API/v1/brains/tenant:acme/records/acme" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "fields": { "name": "Acme" } }'curl -X POST "$API/v1/brains/tenant:acme/statements" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -H "idempotency-key: stmt-salesforce-arr" \ -d '{ "subject": "acme", "predicate": "contract_value_usd", "value": 120000, "source": "hubspot-deal-412" }'
curl -X POST "$API/v1/brains/tenant:acme/statements" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -H "idempotency-key: stmt-netsuite-arr" \ -d '{ "subject": "acme", "predicate": "contract_value_usd", "value": 145000, "source": "netsuite-renewal" }'
curl -X POST "$API/v1/brains/tenant:acme/context" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "prompt": "What is Acme's contract value?", "maxTokens": 2000, "format": "items" }'{ "data": { "text": "", "citations": [], "items": [ { "kind": "record", "id": "acme", "fields": { "contract_value_usd": 120000 }, "status": "contested" } ], "staged": 0, "warnings": [ { "kind": "contested", "subject": "acme", "predicate": "contract_value_usd", "count": 2 } ], "receipt": "rcp_contested", "truncated": false }}ground: "assertion" is the other warrant: you stake the claim; the principal
comes from the key, never from the body. See Facts.
Everything below is the same three calls in different proportions.
The same three calls at three scales
Section titled “The same three calls at three scales”One Brain, many clients
Section titled “One Brain, many clients”Keep one Brain and give each tool its own key, so revocation and attribution stay precise:
# A read-only key for your web appcurl -X POST "$API/v1/brains/tenant:acme/keys" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "name": "web app", "capabilities": ["read"] }'{ "data": { "key": { "id": "key_7f3a1c", "name": "web app", "capabilities": ["read"], "status": "active", "createdAt": "2026-08-14T09:15:41Z" }, "secret": "nbk_9f2c…" }}secret is shown once. See Handing out keys.
A team
Section titled “A team”One Brain per project or account, all on the same schema so a slug (or
/context prompt) written once means the same thing against every one of them.
nicia-core is the neutral vocabulary — people, organizations, topics, events,
notes — which is enough for shared kinds and returned fields without anyone
authoring an ontology first. Non-slug /query filters still wait on a property
index:
curl -X PUT "$API/v1/brains/project:atlas" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "name": "Atlas", "schema": "nicia-core", "mode": "reviewed" }'mode: "reviewed" is what turns “my agents write to my notes” into “my agents
propose, I accept.” Your review queue is the diff.
An AI company: one Brain per customer
Section titled “An AI company: one Brain per customer”// Provisioning is a pure function of your own tenant id.async function brainFor(tenantId: string): Promise<string> { const handle = `tenant:${tenantId}`; await fetch(`${API}/v1/brains/${encodeURIComponent(handle)}`, { method: "PUT", headers: { authorization: `Bearer ${process.env.NICIA_KEY ?? ""}`, "content-type": "application/json", }, body: JSON.stringify({ name: tenantId }), }); return handle; // your handle is the address — nothing to store, nothing to map}Because your handle is the address and PUT is idempotent, there is no
id-mapping table, no create/get branch, and no duplicate Brain to reconcile. See
One Brain per customer.