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.
Five tiers, and a continuous climb
Section titled “Five tiers, and a continuous climb”| 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
kindis a property you attach later. Turning an untyped record into a typedorganizationdoes 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.
Which tier am I?
Section titled “Which tier am I?”| 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 |
The default: nicia-base
Section titled “The default: nicia-base”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 (person → company), invested_in (person or company →
company or deal), founded (person → company), advises (person →
person 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.
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" } }'Untyped records
Section titled “Untyped records”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.
The neutral vocabulary: nicia-core
Section titled “The neutral vocabulary: nicia-core”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:
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 (event → person or
organization), works_at (person → organization). 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.
Custom
Section titled “Custom”When you know your ontology, declare it inline at create time or evolve into it later:
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.
Letting the schema emerge
Section titled “Letting the schema emerge”You do not have to guess. Write documents and untyped records for a while, then ask what shape your data actually has:
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.
Changing your mind
Section titled “Changing your mind”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.
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.
- Records — writing against the vocabulary.
- Custom schemas — declaring and evolving your own.
- Search and queries — what typed fields buy you at read time.