Skip to content

Facts, contradictions and health

Read the atomic facts Cortex extracted, review conflicts between documents, and track a health score for the knowledge base.

Search finds passages. Once a knowledge base holds hundreds of documents, three other questions matter: what exactly do my documents assert, do two of them disagree, and can I still trust what comes out of this corpus. Cortex answers them at ingestion time and exposes the results here, so a workflow can act on them instead of discovering a bad answer in front of a client.

  1. Source ingested
  2. Atomic facts (with provenance)
  3. Compared with other sources
  4. Contradiction (review queue)
  5. Resolved by a person
  6. Health score
Where the three objects come from. Facts and QA pairs are produced during ingestion; contradictions are found by comparing new facts with facts from other sources.

Facts

A fact is one atomic claim taken verbatim from a document (for example, the lease runs until 30 September 2028), with the source and typed entities it mentions (people, companies, dates...). Each fact has a status:

Status Meaning
active Current and not in conflict.
disputed Part of an open contradiction with a fact from another source.
stale Its validity window (validUntil) has passed. An hourly sweep moves expired facts here and fires the knowledge.stale webhook.
retired Set aside when a contradiction was resolved in favour of the other fact.

GET/v1/knowledge/facts

List extracted facts with provenance.

API keyknowledge

Newest first, at most 200 per call, with no pagination: narrow with the filters instead of fetching everything. Use it to build a structured view of a client file, to feed a downstream system with sourced values, or to find facts that went stale.

Query parameters

  • statusstring
    Only facts in this status.
    activedisputedstaleretired
  • sourceIdstring
    Only facts extracted from this source.
  • qstring
    Case-insensitive substring match on the fact statement.

Request examples

curl "https://api.subsidia.protypa.fr/v1/knowledge/facts?status=stale&q=lease" \
-H "Authorization: Bearer $SUBSIDIA_API_KEY"

Responses

The facts.

Example response
{
"facts": [
{
"id": "fct_41ac9e",
"statement": "The commercial lease runs until 30 September 2028.",
"confidence": 0.92,
"status": "active",
"validFrom": "2025-10-01T00:00:00.000Z",
"validUntil": "2028-09-30T00:00:00.000Z",
"createdAt": "2026-10-09T08:14:02.000Z",
"source": { "id": "clx9k2m4p0002abcd", "name": "contrat-bail-2025.pdf" },
"entities": [{ "id": "ent_77", "name": "Dupont SARL", "type": "organization" }]
}
]
}

Errors

  • 401Missing or invalid key.
  • 403scope_deniedThe key lacks the knowledge scope.

Notes

validFrom and validUntil are null when the document states no validity window. confidence is the extractor self-assessment, 0 to 1.

Contradictions

When new facts arrive, each is compared with the active facts of other sources (conflicts inside a single document are mostly extraction noise and are ignored). Candidate pairs are proposed by embedding similarity, then judged by a model, which gives a one-line explanation and a severity. A confirmed conflict becomes a contradiction in the review queue, both facts become disputed, and the knowledge.contradiction_detected webhook fires.

The judge fails open: if it is unavailable, no contradiction is recorded rather than a doubtful one.

GET/v1/knowledge/contradictions

List contradictions between documents.

API keyknowledge

Newest first, at most 100. Use ?status=open as your review queue: each entry shows the two facts side by side with the document each comes from, so a reviewer can decide in one look.

Query parameters

  • statusstring
    Only contradictions in this state. Omit for all.
    openresolveddismissed

Request examples

curl "https://api.subsidia.protypa.fr/v1/knowledge/contradictions?status=open" \
-H "Authorization: Bearer $SUBSIDIA_API_KEY"

Responses

The contradictions.

Example response
{
"contradictions": [
{
"id": "ctr_5d20",
"explanation": "The two documents give different notice periods for terminating the lease.",
"severity": "high",
"status": "open",
"resolution": null,
"createdAt": "2026-10-09T08:14:09.000Z",
"resolvedAt": null,
"factAId": "fct_41ac9e",
"factBId": "fct_9b13d0",
"factA": { "id": "fct_41ac9e", "statement": "Notice period: six months.", "status": "disputed", "source": { "id": "clx9k2m4p0002abcd", "name": "contrat-bail-2025.pdf" } },
"factB": { "id": "fct_9b13d0", "statement": "Notice period: three months.", "status": "disputed", "source": { "id": "clx9k2m4p0007efgh", "name": "avenant-2026.docx" } }
}
]
}

Notes

severity is low, medium or high.

POST/v1/knowledge/contradictions/:id/resolve

Record the decision on an open contradiction.

API keyknowledge

Resolution is a human decision, so the API only records it and updates the two facts accordingly. Only open contradictions can be resolved: resolving twice returns 404.

action Fact A Fact B Contradiction becomes
keep_a active retired resolved
keep_b retired active resolved
both active active resolved (both are true, for example different periods)
dismiss unchanged unchanged dismissed (false alarm)

The resolution field stores the action, followed by your note when you give one (500 characters kept).

Path parameters

  • idstringrequired
    Contradiction id.

Request body

  • actionstringrequired
    The decision.
    keep_akeep_bbothdismiss
  • notestring
    Why, for the record. Appended to resolution.

Request examples

curl -X POST https://api.subsidia.protypa.fr/v1/knowledge/contradictions/ctr_5d20/resolve \
-H "Authorization: Bearer $SUBSIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "keep_b", "note": "The 2026 amendment supersedes the lease."}'

Responses

The updated contradiction.

Example response
{
"contradiction": {
"id": "ctr_5d20",
"status": "resolved",
"resolution": "keep_b: The 2026 amendment supersedes the lease.",
"resolvedAt": "2026-10-09T09:02:31.000Z"
}
}

Errors

  • 400Invalid or missing action.
  • 403read_only_keyThe key is read-only.
  • 404Not found or not open.

Notes

Resolving does not delete either source. Retired facts stay readable with status=retired.

Health

The health score answers a question every team eventually asks: is this corpus still in good shape? It blends two measurements:

  • Answerable rate (70 percent). At ingestion Cortex writes questions whose answer is in a known passage. Replaying them through retrieval shows how many are still found: a drop means documents were added that drown the old ones, or a source was damaged. The replay uses embeddings and full-text search only, so it costs no model call.
  • Hygiene (30 percent). Open contradictions (up to 50 percent penalty), stale facts (up to 25 percent) and failed sources (up to 25 percent).

The score is an integer from 0 to 100, or null when there is nothing to measure. With no replayed question yet, it falls back to the hygiene rate alone.

GET/v1/knowledge/health

Get the health score and its components.

API keyknowledge

Computed on read from the current state, so it always reflects open contradictions and failed sources right now. The answerable part reflects the last replay (see below), which also runs by itself once a day.

Request examples

curl https://api.subsidia.protypa.fr/v1/knowledge/health -H "Authorization: Bearer $SUBSIDIA_API_KEY"

Responses

The health object.

Example response
{
"score": 88,
"answerable": { "rate": 0.94, "passed": 47, "failed": 3, "total": 50, "lastCheckedAt": "2026-10-09T03:00:12.000Z" },
"hygiene": { "rate": 0.75, "openContradictions": 2, "staleFacts": 1, "disputedFacts": 4, "failedSources": 0 },
"corpus": { "sources": 38, "chunks": 1204, "facts": 612, "entities": 187, "qaPairs": 50 }
}

Notes

answerable.rate is null until a replay has checked at least one question.

POST/v1/knowledge/health/replay

Re-run the health questions now.

API keyknowledge

Runs every stored question (up to 200 per call) through retrieval and records pass or fail. A question passes when its original passage is in the top 5, or when its source ranks in the top 3. Use it right after a large import or a deletion to see the effect at once instead of waiting for the daily run. Returns the replay summary and the refreshed health.

Request examples

curl -X POST https://api.subsidia.protypa.fr/v1/knowledge/health/replay -H "Authorization: Bearer $SUBSIDIA_API_KEY"

Responses

The replay result and the new health.

Example response
{
"replay": { "total": 50, "passed": 47, "failed": 3 },
"health": { "score": 88, "answerable": { "rate": 0.94, "passed": 47, "failed": 3, "total": 50, "lastCheckedAt": "2026-10-09T09:10:44.000Z" } }
}

Notes

The replay is synchronous and proportional to the number of stored questions. Do not call it in a tight loop.

Use it in a workflow

  1. 1
    React to events

    Subscribe to knowledge.contradiction_detected and knowledge.stale webhooks so a person is told when a conflict or an expired fact appears, rather than polling.

  2. 2
    Keep a review queue

    List ?status=open contradictions in your own tool, show the two facts and their documents, and call resolve with the reviewer decision and a note. The note is your audit trail.

  3. 3
    Gate on health

    Before a batch job that relies on the corpus (a monthly report, a client mailing), read GET /v1/knowledge/health and stop or warn below your own threshold.

  4. 4
    Verify what leaves the building

    Facts tell you what the documents assert; the Verificateur checks a text against them claim by claim.

Frequently asked

Why does a document that I know is consistent show a contradiction?

Two documents can legitimately differ (an amendment, two periods). Resolve with both, or dismiss if it is a false alarm. Only conflicts across different sources are raised.

Are retired facts used in answers?

Retired facts are the losing side of a contradiction you resolved. They stay readable for history; filter status=active when you want the current state. Fact status does not delete the source passages, which remain searchable until you delete the source.

Why is the score null?

There is nothing to measure yet: no source, or sources without health questions. Ingest a source with generateQa enabled and run a replay.

Related