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

Facts

A fact is a claim about a subject: Acme’s contract is $120k, Bob works at Y, the handbook says PTO starts on day one. Every fact carries a warrant — either a source (a record you can go and read) or ground: "assertion" (you stake the claim; the principal is the key, never a field you send). Never neither. Never both.

Terminal window
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": "salesforce-renewal"
}'
{
"data": {
"statementIds": ["stmt_91ab01"],
"outcome": {
"kind": "admitted",
"batchId": "batch_44a1e0",
"policyVersionId": "pol_nicia_open",
"acceptedSnapshot": "snap_6f1e4d6b",
"eventIds": ["chg_44a1e0"]
},
"statements": [
{
"id": "stmt_91ab01",
"subject": "acme",
"predicate": "contract_value_usd",
"value": 120000,
"validFrom": "2026-08-14T09:20:00.000Z",
"source": "salesforce-renewal",
"assertedBy": { "kind": "service", "id": "key_7f3a1c" },
"status": "active"
}
]
}
}

source is a record id — the same kebab rule as PATCH /records/{id}. A document id with colons (hubspot:deal:412) is not a source; write or address the record first. assertedBy is projected from the change event. You cannot set it.

The other warrant is a stake:

Terminal window
curl -X POST "$API/v1/brains/tenant:acme/statements" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: stmt-handbook-pto" \
-d '{
"subject": "acme",
"predicate": "pto_starts_on",
"value": "day-one",
"ground": "assertion"
}'

A later fact from the same source supersedes. A different source disagreeing over an overlapping window does not: both facts stay, and the next /context names it. You do not call GET /conflicts to find out. That list is the operator inbox.

Terminal window
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"
}'
Terminal window
curl -X POST "$API/v1/brains/tenant:acme/context" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{
"prompt": "What is the Acme contract value?",
"maxTokens": 2000,
"format": "items"
}'
{
"data": {
"text": "",
"citations": [],
"items": [
{
"kind": "record",
"id": "stmt-salesforce-arr",
"fields": { "contract_value_usd": 120000 },
"status": "contested"
},
{
"kind": "record",
"id": "stmt-netsuite-arr",
"fields": { "contract_value_usd": 145000 },
"status": "contested"
}
],
"staged": 0,
"warnings": [
{
"kind": "contested",
"subject": "acme",
"predicate": "contract_value_usd",
"count": 2,
"detail": "Two sources disagree about acme.contract_value_usd."
}
],
"receipt": "rcp_contested",
"truncated": false
}
}

warnings[] kind contested names the subject and predicate. Per-item status: "contested" marks the facts in this answer. There is no conflicts[] field on /context.

GET /conflicts is the inbox. POST /conflicts/{id}/resolve keeps one fact or writes a corrected value. The loser is retained, not deleted. The call needs review and an Idempotency-Key.

Terminal window
curl "$API/v1/brains/tenant:acme/conflicts?resolved=false" \
-H "authorization: Bearer $NICIA_KEY"
{
"data": {
"conflicts": [
{
"id": "cfl_91ab",
"subject": "acme",
"predicate": "contract_value_usd",
"statements": [
{
"id": "stmt_91ab01",
"subject": "acme",
"predicate": "contract_value_usd",
"value": 120000,
"validFrom": "2026-08-14T09:20:00.000Z",
"source": "salesforce-renewal",
"assertedBy": { "kind": "service", "id": "key_7f3a1c" },
"status": "conflicted"
},
{
"id": "stmt_91ab02",
"subject": "acme",
"predicate": "contract_value_usd",
"value": 145000,
"validFrom": "2026-08-14T09:21:00.000Z",
"source": "netsuite-renewal",
"assertedBy": { "kind": "service", "id": "key_7f3a1c" },
"status": "conflicted"
}
],
"detectedAt": "2026-08-14T09:21:01.000Z"
}
],
"receipt": "rcp_conflicts"
}
}
Terminal window
curl -X POST "$API/v1/brains/tenant:acme/conflicts/cfl_91ab/resolve" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: resolve-acme-arr" \
-d '{ "keep": "stmt_91ab02" }'
{
"data": {
"outcome": {
"kind": "admitted",
"batchId": "batch_91ab03",
"policyVersionId": "pol_nicia_open",
"acceptedSnapshot": "snap_91ab03",
"eventIds": ["chg_91ab03"]
}
}
}

Or send "value": 145000 instead of keep to supersede both with a corrected number.

  • Context — how a contested fact shows up on the read.
  • Receipts — who was told, and whether a receipt is stale.
  • Review — Open vs Reviewed, and settling the queue.
  • Records — the subjects and sources facts point at.