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

Schemas

A schema is the vocabulary a Brain uses for structured data: which kinds of record exist, what fields they have, and how they link. It decides what you can filter and sort on. It never decides what you can store — nothing you send is ever dropped.

You do not need one to start, and you never have to jump.

Tier What you do Good for
None POST /documents and stop Pure retrieval. Prose in, grounded answers out.
Default Nothing — every Brain gets nicia-base Structured writes with zero authoring.
Pack Name a maintained domain schema — Planned Your domain has a well-known shape.
Custom "schema": { … } You know your ontology and want it queryable.

Two guarantees make this a ramp rather than five doors:

  • The address never changes. A record is addressed by an id you choose, and kind is a property you attach later. Turning an untyped record into a typed organization does not move it, so nothing built against it breaks.
  • Nothing is lost on the way in. Every key you send is stored and round-trips. Declaring a field later makes values that were already there filterable — promotion is retroactive, not forward-only.

Together those mean a partial or nearly-complete model can come over as-is, the parts that do not fit our shape are kept rather than dropped, and you finish it here at whatever pace the work justifies. No re-import, no migration step.

If you have… Start at
A pile of markdown None — documents, and stop
Structured markdown you curated or an LLM organised Untyped records — preserve your ids and fields without choosing kinds
Prose plus some fields and ids from other systems Untyped records — add a kind only when you need exact queries
A domain model that mostly works Custom — declare only what you need queryable today

Every Brain starts here unless you say otherwise. It classifies documents into kinds by folder and frontmatter type — it declares no typed fields of its own, so nothing is queryable on it yet. Attach fields with a custom schema the moment you want to filter on one; every value you send still stores and round-trips before that.

Kind Holds
person Founders, operators, investors, contacts
company Companies and organizations
deal Investments and transactions
concept Reusable ideas, themes, and topics
note Free-form notes and documents

Links: works_at (personcompany), invested_in (person or companycompany or deal), founded (personcompany), advises (personperson or company), plus a document-to-document mentions link and a document-to-concept reference link, both inferred from wikilinks rather than declared frontmatter fields.

Terminal window
curl -X PATCH "$API/v1/brains/tenant:acme/records/renewal-call-mar-2026" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "fields": { "notes": "Renewal call, 2026-03-14" } }'

Most structured data does not fit a tidy entity, and most teams do not yet know what their entities are. A record written with no kind carries no kind at all — not a placeholder kind, just absent — and it is still a perfectly useful record: it has your id, it can carry fields, and it can be related to other records.

{ "id": "crm-account-4471", "fields": { "arr": 120000 } }

That is a complete, useful state. You never have to decide what something is in order to say something about it — and when you do decide, attaching the kind is one PATCH that leaves the address alone.

nicia-base above carries the platform’s original investor-flavoured names (company, deal). If you want a domain-neutral vocabulary instead, select it explicitly at create time — it is not the default:

Terminal window
curl -X PUT "$API/v1/brains/tenant:acme" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Acme", "schema": "nicia-core" }'
Kind Fields Holds
person name, email, role, org Humans
organization name, domain, segment Companies, teams, accounts
topic name, summary Products, concepts, subjects
event title, occurredAt, category, summary Meetings, tickets, incidents, releases
note title Free-form prose

Links: about (any kind → topic), involves (eventperson or organization), works_at (personorganization). There is no subject kind here either — an untyped record stays kind-absent under nicia-core too.

Every field is optional. Send one field or all of them.

A pack is a schema we maintain for a common domain, so a team whose shape is already well known does not author one. Use the same pack across a fleet and one query works against every Brain in it.

The Standing Answers kinds (statement, standing_question, standing_answer) exist as a pack in the platform, but they are merged into a Brain’s existing schema rather than chosen at create time, so they are not a "schema" value either.

When you know your ontology, declare it inline at create time or evolve into it later:

Terminal window
curl -X PUT "$API/v1/brains/tenant:acme" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Acme",
"schema": {
"extends": "nicia-base",
"kinds": [
{ "name": "deployment",
"fields": [
{ "name": "environment", "type": "enum", "values": ["dev", "staging", "prod"] },
{ "name": "released_at", "type": "string" },
{ "name": "rollback", "type": "boolean" }
] }
],
"links": [
{ "name": "deployed_by", "from": ["deployment"], "to": ["person"] }
]
}
}'

Field types are string, number, boolean, enum, and string_list. Keep it small: a handful of kinds with a handful of fields each extracts far more reliably than a sprawling ontology. Start generic and add as you learn.

See Custom schemas for evolving a schema on a Brain that already holds data.

You do not have to guess. Write documents and untyped records for a while, then ask what shape your data actually has:

Terminal window
curl "$API/v1/brains/tenant:acme/schema/candidates" \
-H "authorization: Bearer $NICIA_KEY"
{
"data": {
"kinds": [
{
"name": "deployment",
"occurrences": 412,
"fields": [
{
"name": "environment",
"type": "enum",
"values": ["dev", "staging", "prod"],
"coverage": 0.97
}
]
}
],
"predicates": [
{
"predicate": "health_score",
"occurrences": 1840,
"type": "number",
"coverage": 0.64,
"suggestion": "promote to a field on organization"
}
]
}
}

Promote what looks right, ignore the rest. This is the recommended path for anyone who is not certain of their model on day one — which is almost everyone.

Adding kinds, adding fields, and widening an enum are safe and take effect immediately. Removing a kind that holds records, or narrowing a field’s type, is a 409 conflict — Nicia will not silently discard data you already wrote.

Terminal window
curl -X PATCH "$API/v1/brains/tenant:acme/schema" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "kinds": [{ "name": "person", "fields": [{ "name": "timezone", "type": "string" }] }] }'

Records written before a field existed keep the value they were sent with — it becomes queryable the moment the field is declared, because nothing was dropped on the way in.