HTTP API
JSON over HTTP at http://127.0.0.1:8787/api/v1. It runs the same engine and knowledge base as the MCP server, so evaluate returns what the evaluate_statement tool returns, with three additions: a job_id, an evidence chain for each claim, and a description of the run.
The service's contract (v1, OpenAPI 3.1) is /api/v1/openapi.json, also docs/api.yaml in the repository: POST /api/v1/checks and the job, facts, norm-review, definition, readiness and metrics routes, with Bearer keys, scopes and tenants. The endpoints below are the website's own.
Endpoints
| Endpoint | Key | What it does |
|---|---|---|
| POST/api/v1/evaluate | yes | Fast pass, synchronous. Body: {text, thorough?, check_framing?, context?, store_premises?, domain?, site?}. The request has a hard timeout (150 s by default). If the pass is not done by then, the response is 504 with the job_id, and the evaluation carries on as a job. fill_gaps > 0 is refused here: use /jobs. |
| POST/api/v1/jobs | yes | Any pass, asynchronous. Body: {text, thorough?, check_framing?, fill_gaps?, context?, store_premises?, domain?, site?, lang?} (lang: the language to answer in, default the text's own; context: "default", a context id, "text_own" or a list of {point, value}; an unknown one is 400). Returns 202 with {job_id, poll} straight away. |
| POST/api/v1/check | yes | A check under a domain, at a site: as /jobs, with {text, domain, site, fill_gaps?, thorough?, check_framing?, context?, store_premises?}. domain is general (default) or si-building; site is a parcel {"ko": 992, "parcel": "929/669"}, a point {"lon": 15.0705, "lat": 46.2810} or a string "992/929/669", and needs a domain whose norms apply at sites (400 otherwise; an unknown domain is 400 too). The site is resolved to its planning units first, its claims are read against the norms in force there, and each such claim carries norm. See Sites and norms. /evaluate and /jobs take domain and site as well. |
| GET/api/v1/sites/resolve | yes | A site's card from Bayesian's own spatial store (spatial/spatial.sqlite next to kb.json, or the one BAYESIAN_PLACE_RESOLVER=local:<path> names): ?parcel=992/929/669 (or ?ko=992&parcel=929/669) or ?lon=15.070335&lat=46.280957 (WGS 84). The PlaceCard: municipality, parcels (with area_m2), units (EUP code, PEUP sub_code, land_use, share of the site's area, overlap area_m2, act with the base plan's key, oppn_status, snapshot), regimes (kind, class, name, share, legal basis), acts in force, snapshots per layer, source. 400 for a malformed place, 404 when the store has nothing for it, 503 without a store. See Sites and norms. |
| POST/api/v1/review | yes | Text review, synchronous (one parse, no research, the hard timeout). Body: {text, thorough?, check_framing?}. Returns {claims[], premises[], totals, warnings, kb_revision, prompts}. |
| GET/api/v1/choice-points | no | Every choice point (drafts included) with its values, priors, bases, default and flags, and the named contexts: {revision, choice_points[], contexts[]}. |
| POST/api/v1/choice-points | yes | Add or replace a choice point, {id, gloss, kind, values: [{id, gloss?, prior, basis?}], default?} (priors are normalised; a draft with the same id is accepted), or set its default, {id, default, forced?}. Journaled with your key. Returns {choice_point, revision}. |
| GET/api/v1/contexts | no | The named contexts, {revision, built_in: ["default", "text_own"], contexts[]}. |
| POST/api/v1/contexts | yes | Add or replace a named context, {id, gloss?, assumptions: [{point, value}]} (at most one value per point). Journaled. Returns {context, revision}. |
| GET/api/v1/domains | no | The domain profiles (id, gloss, research, time_policy, factors with key, gloss and unit, sites: whether its norms apply at sites), and the corpus and place resolver this server reads (corpus, place_resolver; null when not configured; local:…/corpus/corpus.sqlite is Bayesian's own store, the default). |
| GET/api/v1/corpus/documents | no | The acts in Bayesian's own corpus store (?municipality=173: a municipality's), each with id, title, cite_as, kind, municipality, publication, url, role, versions (id, label, valid_from, valid_to, status, source_url, covers: the publications it consolidates), provenance (source, e.g. “Luma Intelligence”, via, primary: the primary records behind it with role, URL, label, publication, version, unofficial; history; ids), snapshot, imported_at and counts per version (units, paragraphs, annex rows, norms). 404 when the server has no store. |
| GET/api/v1/corpus/documents/{id}/units | no | A version's units in document order: ?version= (default: the version in force), ?q= (only the units whose text matches, best first, as a check searches), ?kind= (comma-separated: article, paragraph, annex, annex_row, transitional). Returns {document, title, version, q, count, units: [{kind, no, paragraph?, item?, title, text, ordinal, context}]}; an article's numbered paragraphs follow it as units of kind paragraph. 404 for an unknown document or version. |
| GET/api/v1/corpus/search | no | Ranked hits for ?q= in the versions in force of ?docs= (comma-separated; default every stored act), ?limit= (50, at most 200): {q, docs, total, count, hits: [{doc, version, no, item?, title, snippet, text, context, rank}]}. The same search a site check makes, so a client can cite the text a verdict used. 400 without q. |
| GET/api/v1/corpus/status | no | The store's path, schema_version, counts (documents, versions, units, paragraphs, annex_rows, norms, plan_units, plan_unit_keys) and last_imports (per source: when, the snapshot date, what was asked, the counts). |
| POST/api/v1/jobs/{id}/investigate | yes | Research a finished job's open gaps further, on the claims it already has: nothing is parsed again. Body: {fill_gaps, deeper?, context?} (context default: the one the result was judged in; 0 to BAYESIAN_WEB_MAX_FILL research sessions; 0 is a re-evaluation: no research, mode re-evaluation; deeper default true; thorough is accepted and has no effect). Returns 202 with {job_id, status, kind: "investigate", poll, parent, parent_verdict, fill_gaps, fill_budget_left_today}. The new job has the same text, kind investigate, mode investigation and parent (the job it started from); its result has the shape of any job's. Investigating an investigation chains from its result, which has the latest state. 409 when the job is not done, has no open gaps, or was stored before results kept their parse (check the text again and investigate that). See Investigating open gaps. |
| GET/api/v1/jobs/{id} | yes | status (queued, running, done, failed or interrupted), the submitted text and options, timings, verdict_id once published, kind (check or investigate), parent and parent_verdict for an investigation, and result once done (with investigable: whether its parse is kept, so it can be investigated). Jobs are kept for 24 hours and survive a restart (see what is kept where); a job that was queued or running when the server stopped comes back as interrupted, without a result. With API keys, only the key that created a job can read it. |
| GET/api/v1/jobs | yes | The jobs this server keeps, newest first: {jobs, total, limit, kept}. Each line has job_id, status, mode (fast, verified or investigation), kind (check or investigate), parent (the job an investigation started from) and parent_verdict, fill_gaps, created (ISO UTC), seconds, preview (the first 80 characters of the text, not HTML-escaped), chars, claims, summary (claims per label) and gaps (open gaps) once done, verdict_id once published, and error. No results: fetch /jobs/{id} for one. Query: limit (default 50, at most 500), status (one or more, comma-separated), ids (comma-separated job ids; ids the server no longer has are left out). total counts the matching jobs before the limit. With API keys, only the caller's jobs are listed; without, every job on the server. |
| POST/api/v1/verdicts | yes | Publish a finished job as a permanent verdict. Body: {job_id, commissioned_by?, title?}. Returns 201 with {id, url, version}. Only results this server computed can be published. Publishing the same job twice returns the first verdict. An investigation whose chain was published (the job it started from, or one further up, is a verdict) becomes the next version of that verdict, keeping its title and commissioner unless new ones are given; earlier versions stay as they are. |
| GET/api/v1/verdicts | no | Every verdict on the server, samples first: id, title, mode, counts and label summary. |
| GET/api/v1/verdicts/{id} | no | A stored verdict, its latest version: text, claims, opinions, evidence chain, framing, parameter fingerprint, commissioner and timestamps, with version, latest_version and, when there are several, versions[] (version, published, job_id, commissioned_by, mode, fill_gaps, seconds, gaps, summary). ?version=n gives an earlier one. The page for humans is /v/{id} (/v/{id}?v=n for version n). |
| POST/api/v1/verdicts/{id}/investigate | yes | As /jobs/{id}/investigate, from the verdict's latest version (useful once its job is gone). Publishing the new job adds a version to this verdict, with a per-claim diff against the version before. {"fill_gaps": 0} re-evaluates it against the current knowledge base. |
| GET/api/v1/verdicts/{id}/render | no | The render made for a version in a language: ?lang=sl (else Accept-Language), &version=n. {lang, made, texts: {English → rendering}, predicates: {key → {key, en, text}}, missing, model_call}; 404 when none was made. GET /api/v1/verdicts/{id}?lang=sl includes it as render. |
| POST/api/v1/verdicts/{id}/render | yes | Make it: one model call for the version's prose the render cache lacks (notes, glosses, hedges, indicator descriptions, measured items' reasons, the norm's reasons the vocabulary has no pattern for) and readings of its predicate keys; kept at verdicts/{id}.versions/{n}.render.{lang}.json. Quotes and provisions are never translated here. Counts against the evaluation rate. POST /api/v1/jobs/{id}/render?lang= does the same for a finished job's result (not kept per job; the render cache keeps the renderings). |
| GET/api/v1/i18n | no | The fixed vocabulary of a language for pages: ?lang=sl, else the first supported language Accept-Language names, else English. {lang, from (query, accept-language, default), known, name, complete, supported: [{lang, name}], table: {English key → translation}}; keys with {} are patterns. |
| GET/api/v1/verdicts/{id}/changes | no | What changed in the knowledge base since that version was judged: {kb_revision, current_revision, count, events[], note}, the journal events after the version's kb_revision that touch its claims (their rules and evidence items, rules and definitions for their subjects' predicates, their subjects as terms), newest first. ?version=n for an earlier version. The verdict page shows it as “Knowledge changed since this version”. |
| POST/api/v1/amend | yes | Amend the knowledge base, with a reason (required; journaled). Body: {op, targets?, status?, reason, actor?, subject?, predicate?}; op is set_status (with status: active, superseded, stale, retracted or erroneous), retract_evidence, retract_rule, restore, purge, note, norm_state (a planner's review of a norm a model read: state approved, corrected with ways, the norm's ways as they should read, or rejected, which retracts the rule; targets the norm rules; reason the reviewer's note, shown in the Norms section) or add_evidence (a record the caller holds, on the one rule in targets: evidence {source, kind, supports?, published?, quote?, value?, tolerance?, unit?, bound?, directness?, hedge?, assumes?}, the fields of MCP add_evidence; quote is a string or {en, lang, text} with the original in text; the reason goes on the item's note and in the journal; anyone may add, and the item is the caller's, so only the caller may change its status later; returns {report: {items: [the new id], rules}, rule: {id, strength, items}, revision}; a re-evaluation then judges the claim with it). targets are ev:<id> (an evidence item, the ids chains show) and rule:<id>; actor (job:<id>, key:<fingerprint>, …) with optional subject and predicate prefix selects every item it wrote. Returns {report, revision}. With API keys a caller may amend only what its own key or its own jobs wrote: anything else is 403 with the forbidden targets (a note is always allowed); without keys anyone may. See Finding status. |
| GET/api/v1/history | no | The knowledge base's journal, newest first: {revision, events[]}. Query: subject, predicate (prefix), rule, term, item (an evidence id), actor (job:<id>, key:<fingerprint>, mcp:<tool>, prompt:<name>, heal, …), since_seq, limit (default 50, at most 1000). |
| GET/api/v1/explain/{verdict_id}/{claim_index} | no | Explains one claim of a stored verdict against the knowledge base as it is now: the argument chain as text, the current opinion next to the recorded one, and whether it has changed. The published verdict stays as it is. |
| GET/api/v1/stats | no | kb_stats (terms, rules, sources, evidence by kind, corroboration, conflicts, gaps), the parameter version and fingerprint, the engine build, the number of verdicts, and the calibration summary from eval/calib/reports. |
| GET/api/v1/health | no | Liveness, engine build, parameter fingerprint, whether a key is required, the limits (max_fill_gaps per request, daily_fill_gaps, investigate_rounds, …; stats has them too), and running jobs. |
Fast pass
curl -s http://127.0.0.1:8787/api/v1/evaluate \
-H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"text": "The Mongols expanded their business operations to Baghdad in 1258."}'
Verified pass (async)
# 1. start: up to five research sessions (what the check page sends)
curl -s http://127.0.0.1:8787/api/v1/jobs -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"text": "…", "fill_gaps": 5}'
# → 202 {"job_id": "3f9a…", "status": "queued", "poll": "/api/v1/jobs/3f9a…", "fill_budget_left_today": 195}
# 2. poll every few seconds until status is done or failed
curl -s http://127.0.0.1:8787/api/v1/jobs/3f9a… -H "X-Api-Key: $KEY"
# 3. publish it as a permanent verdict, naming who asked for it
curl -s http://127.0.0.1:8787/api/v1/verdicts -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"job_id": "3f9a…", "commissioned_by": "Example Newsroom"}'
# → 201 {"id": "b41c…", "url": "/v/b41c…", "api": "/api/v1/verdicts/b41c…"}
# 4. later: your recent jobs, newest first (or ?status=done, ?ids=3f9a…,77c0…)
curl -s 'http://127.0.0.1:8787/api/v1/jobs?limit=10' -H "X-Api-Key: $KEY"
# → {"jobs": [{"job_id": "3f9a…", "status": "done", "mode": "verified", "fill_gaps": 5,
# "created": "2026-10-16T12:00:00Z", "seconds": 184.2, "preview": "…", "chars": 1840,
# "claims": 23, "summary": {"likely true": 4, "insufficient evidence": 19},
# "verdict_id": "b41c…", "error": null}], "total": 1, "limit": 10, "kept": {"hours": 24, "max": 500}}
Investigating open gaps
A verified pass researches the most checkable gaps within its budget and tries each gap once. What is still open (gaps) can be followed up with as many sessions as you choose, up to BAYESIAN_WEB_MAX_FILL (50 by default, practically unlimited for one text). On the check page and on a verdict page this is the “Investigate open gaps” button under the gap list.
# follow up a finished job (or POST /api/v1/verdicts/b41c…/investigate for a published verdict)
curl -s http://127.0.0.1:8787/api/v1/jobs/3f9a…/investigate -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"fill_gaps": 20}'
# → 202 {"job_id": "8d02…", "kind": "investigate", "parent": "3f9a…", "parent_verdict": "b41c…", "poll": "/api/v1/jobs/8d02…", …}
# poll it like any job; publish it: it becomes version 2 of verdict b41c…
curl -s http://127.0.0.1:8787/api/v1/verdicts -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" -d '{"job_id": "8d02…"}'
# → 201 {"id": "b41c…", "url": "/v/b41c…", "version": 2, …}
- An investigation judges the stored claims against today's knowledge base (nothing is parsed again), collects the gaps as a verified pass does (framed claims' standard characterisations and definitions' indicators included) and fills them in rounds of plan, research and re-judge, at most 10, until the sessions are used up, no gap is left to try, or the round limit; the last line of
filledsays which and how many deeper retries ran. - Each gap is tried once per investigation. A gap an earlier try left open or thin (weak evidence, or research that found no record, in the earlier pass or an earlier round) gets one deeper retry instead: research that is told what the knowledge base already has and asked to look elsewhere (primary records, the subject's own channels, other languages, other outlets), or, for a measured indicator whose sample came out below the definition's size, a wider sample (about a year instead of the last weeks, more channels, the sampled items excluded) that takes in the earlier items and replaces the smaller sample. Each retry is one session; in
filledits line reads “…: deeper retry: …”. deeper: falseturns the retries off: then only gaps not yet tried in this investigation are researched.
Premises and contexts
Some verdicts rest on a choice the evidence does not settle: which definition of a contested word applies (genocide: the Convention's specific intent, or any grave war crime), which reading of a text, whether a substantive premise holds. A choice point names such a choice and its values, mutually exclusive and exhaustive, each with a prior (normalised to sum to 1) and its basis. It is stored as a term choice:<id> with one child term per value, choice:<id>:<value>, whose base_rate is the prior. Knowledge that holds under one value only says so with assumes: an evidence item lands on the rule conditional on that value's term, a definition is one reading of its predicate (several may be accepted at once when they differ in assumes; accepting a second one without them fails and names the choice point to create), and an indicator's evidence inherits its definition's reading.
- The default context is the headline: every point at its default value, and a point the claim leaves open is marginalised: the verdict is the prior-weighted mixture of the verdicts under each value, by the same moderator mixture that splits conflicted rules (a value with no rules of its own takes the rules that hold whatever the value). A default set with
forced: true(a legal default) is fixed instead. - A context fixes points: its values' rules decide and the rules on the other values are left out (the claim's flags say so).
contextis"default", a named context (POST /api/v1/contexts),"text_own"(the premises the text states, resolved to known points, and each claim's ownassumes) or a list of{point, value}. - Every claim lists its
assumptions: each choice point touched anywhere in its chain (rules, branches,givenpremises, definitions and their indicators, claims of the same text it depends on),{point, value, how, prior, where}withhowfixed,marginalised(the value is the default) ordecisive_by_evidence(marginalised, but every value gives the same label), andwherethe rule ids,definition '…',indicator …orclaim #nwhere it enters. - And its
alternatives, "under other readings": the claim judged again with one touched point at one of its values, one point at a time, on the same knowledge (no research, nothing recorded), at mostcontexts.max_alternatives(8):{assumptions, point, value, prior, label, probability, shown, delta}. They are shown after the headline, each with its prior, and are not equally valid readings. - A run records its context (
run.context); a later version judged in another context says so first in itsdiff(context_before,context_after, "judged in another context (seat → default): the differences come from the context, …"). - The parser lists the premises a text states in
premises({text, point?, value?, new_point?}); a premise on no known point is a proposal, stored as a draft choice point only withstore_premises: true. - A definition proposed for a contested predicate (
fill_gaps,operationalize) brings a draft choice point<predicate>_definitionwith one draft definition per reading. Ids are lowercase snake_case of at most 64 characters: for a predicate over 53 characters the id is cut at a_before the suffix (counts_in_kants_classification_as_judgment_of_definition), with six hex digits of the predicate's hash before the suffix when another predicate's point already has that id.
curl -s http://127.0.0.1:8787/api/v1/choice-points -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" -d '{
"id": "genocide_definition", "gloss": "what genocide means", "kind": "definition",
"values": [{"id": "convention_intent", "prior": 0.7, "basis": "the Genocide Convention, ICJ practice"},
{"id": "broad_war_crimes", "prior": 0.3, "basis": "usage in advocacy and part of the literature"}]}'
curl -s http://127.0.0.1:8787/api/v1/check -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"text": "…", "context": [{"point": "genocide_definition", "value": "broad_war_crimes"}]}'
Text review
POST /api/v1/review (and the check page's Review mode, MCP review_text) parses the text, types every claim (kind), lists the premises it states and whether they hit known choice points, and judges every researchable claim (facts and attributions, not about the text itself) twice: in the default context, the headline, and in the text's own. Per claim: {index, claim, subject, predicate, kind, researchable, default: {label, probability, shown}, own: {…}, own_assumes, changed, contested, assumptions}. Totals: claims, by_kind, researchable, framework_dependence (the share of judged claims whose label changes between the two contexts), contested_share (resting on contested rules), premises, premises_on_known_points. No research; nothing is recorded but the terms the parse needs.
Facts supplied by the caller, and questions
Bayesian does not turn a parcel number or coordinates into planning units: a check at a site it has no card for answers with a question, and the caller answers with data. Every result lists questions: locate_site (a site to locate: municipality, units with land use and share, regimes, acts in force; answered by facts.site_card), norm (a factor at a known site with no norm in force, naming the units and their land uses; answered by facts.norms) and source (a statement with no rule; answered by facts.sources), each with id, ask, needs, answer and the claims it blocks.
# answer with the check itself (or on POST /api/v1/jobs/{id}/investigate, or store first with POST /api/v1/facts)
curl -s http://127.0.0.1:8787/api/v1/jobs -H 'Content-Type: application/json' -d '{
"text": "…FZ 0,19, višina 5,75 m, etažnost P…",
"facts": {
"supplier": "Luma Intelligence",
"luma_location": { …the answer of Luma's POST /v1/location/resolve… },
"luma_plan_acts": { …the answer of GET /v1/plan-acts?ob=42… },
"norms": [{ "id": "…", "doc": "pa-444", "version": "1", "municipality": 42, "scope": "unit", "unit_keys": ["GA01"],
"factor": "fz", "operator": "max", "polarity": "required", "value": 0.4, "unit": "ratio",
"text": "…verbatim…", "locus": { "corpus": "facts", "document": "pa-444", "version": "1", "unit": "52",
"cite_as": "52. člen Odloka o OPN Občine Juršinci", "url": "…" },
"confidence": "high", "support": "verbatim", "state": "approved" }],
"sources": [{ "text": "…GZ-1 141. člen verbatim…", "source": "GZ-1, 141. člen", "url": "https://pisrs.si/pregledPredpisa?id=ZAKO8244", "kind": "official_record" }]
}
}'
site_card (the place card) may replace luma_location, and luma_plan_rules (Luma's GET /v1/plan-rules answer) or norms with documents carry the plan. A supplied card is taken instead of any resolver, site defaults to the parcel it names and domain to si-building. Everything is stored with its provenance (journaled under tool facts) and reused by later checks; the result's notes say what was stored.
Reasoning from first principles
POST /api/v1/jobs (or /check) with "principles": true, the check page's fourth mode, MCP first_principles: the parse names the predicates whose truth depends on what a concept means (fascist, democracy, perjury, British as a status; facts and evaluations alike), each concept is analysed into its defining conditions from its authoritative sources (an analytic definition with a logical composition: all conditions necessary, any one sufficient, at_least k of them, or weighted; a contested concept gets a draft choice point with one reading per definition), a condition that is itself a concept is analysed in the next round (at most 8 analyses and 5 rounds; analyses are not research sessions, so fill_gaps: 0 still analyses), research sessions go to the leaves, and the conditions' verdicts compose back up (Jøsang's multiplication and comultiplication for all and any). The result adds derivations, one per claim: a tree of nodes with label, probability, shown and grounding (analysis, record, measurement, sources, recall, judgement, open), the definition used at each analysed node (label, nature, composition, basis, draft), the deciding sources of each ground, and per claim depth, grounds, stipulations (the choice points it rests on), judgements, open and rendering (the tree as text). Mode first principles in the job list and run metadata; an investigation of such a result keeps the mode. Analytic definitions are stored as drafts (accept_definition) and serve every later text.
Journal, revision and provenance
Every change to the knowledge base is one line of an append-only journal, kb.journal.jsonl beside kb.json: {seq, ts, actor, op, targets, before, after, reason}. The line is written in the same locked step as kb.json, first, and kb.json then records revision = seq. Ops: add_rule, add_evidence, merge_rules, alias_predicate, adopt_term, define, accept_definition, measure, heal, set_status, purge, note, trust_update, resolve_conflict, link_terms, set_source_trust, mark_individual, mark_timeless, add_choice_point, set_choice_default, add_context (targets choice:<id>, context:<id>). before and after are compact (rule id, subject, predicate, polarity, strength b/d/u, item count, the added items' ids and sources); a purge keeps the whole object. The journal is kept by size only: past 1 GB (BAYESIAN_JOURNAL_MAX_BYTES) its oldest part moves into kb.journal.1.jsonl.gz, .2, …, which history reads too.
Every evidence item, rule, term, definition and measured sample records when it was stored (added) and by whom (actor: kind job, mcp, seed, manual, heal or fit; the job id or tool; key, the first 8 hex digits of the SHA-256 of the caller's API key, never the key; prompt_version, e.g. research:fde584b0; params_fingerprint; engine). Chains show it per item as by, added, prompt and the item's id; items from before 2026-10-17 show unknown (before 2026-10-17). stats.kb.prompt_versions lists the current prompt versions.
# everything one job wrote, and what happened to one rule
curl -s 'http://127.0.0.1:8787/api/v1/history?actor=job:3f9a…'
curl -s 'http://127.0.0.1:8787/api/v1/history?subject=janez_jansa&predicate=called_for_'
Finding status
An evidence item is active (counts in full), superseded (at recency.superseded_weight, as an item contradicted by a strictly newer one already is, except on a timeless rule), stale (at status.stale_weight, 0.5), retracted (the source withdrew it: counts 0, stays in chains struck through with its reason) or erroneous (never valid: counts 0, left out of chains, of gaps and of source trust; listed by audit and history; its source notes erroneous_items and the last reason). A rule whose items are all retracted or erroneous, or that is withdrawn as a whole, is as if absent: its claims get their gap back, so research can run again, and new evidence on it lifts the withdrawal. Chains carry status, status_reason, status_by and status_at; the check and verdict pages have a “Flag” control on each item, which sets a status or, when your key may not change that item, records your flag as a review note.
# mark one item erroneous
curl -s http://127.0.0.1:8787/api/v1/amend -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"op": "set_status", "targets": ["ev:5c1e0b9a77d2"], "status": "erroneous", "reason": "an absence finding stored as support"}'
# a client adds the record it holds (the neighbours' ridge heights) to the claim's rule, then re-evaluates
curl -s http://127.0.0.1:8787/api/v1/amend -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"op": "add_evidence", "targets": ["rule:site:992-929/669|height_m:8.5_m|stated:qualitative:ridge_height_matches_neighbouring_houses"],
"reason": "kataster stavb: the three neighbouring single-dwelling houses have ridge heights 8.9, 9.2 and 8.6 m (average 8.9 m), so a ridge of 8.5 m does not exceed their average",
"evidence": {"source": "Kataster stavb (GURS), izpis 2026-10-17", "kind": "official_record", "published": "2026-10-17",
"quote": {"en": "ridge heights 8.9 m, 9.2 m, 8.6 m", "lang": "sl", "text": "višine slemen 8,9 m, 9,2 m, 8,6 m"}}}'
# everything one job wrote on a predicate, in one call
curl -s http://127.0.0.1:8787/api/v1/amend -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"op": "set_status", "actor": "job:b29ce76170a56c66", "predicate": "called_for_", "status": "erroneous", "reason": "…"}'
Evidence dates and recency
An item contradicted by a strictly newer item on the other side counts at recency.superseded_weight (0.5) and the verdict flags it superseded: …: a count revised later the same day, a registry entry older than the paper that disagrees with it. This applies only where time can change the answer. A claim is timeless when its truth does not depend on the date it is judged on: an interpretation or characterisation of a work, an idea, a thinker or a past period, a conceptual, definitional or mathematical claim, who first did or said something. The parser gives timeless on every attribution and evaluation, true or false, and on a fact when it is true; left out (a claim parsed before, or given by hand), an attribution or evaluation is timeless and any other kind is not; a claim with its own time, a figure, or a public act never is. Judging or ingesting a timeless claim marks its proposition's rules without a period timeless (journaled mark_timeless; a parse with timeless: false unmarks them). On a timeless rule nothing is superseded by date, its readings are weighed as they are (readings both ways: contested), and about an individual they do not compound as reports of a fact (the frequency model, so readings alone stay below “very likely”). Chain arguments carry timeless: true, their items no superseded_by, and the verdict has one flag per deciding timeless rule with items both ways: timeless claim: on '…' newer sources do not supersede older ones and readings do not compound as reports of a fact; … (1960) counts in full against newer … (2025-01-31). A rule with a period, a figure in its predicate, a counted sample or measured value, a norm or a public act stays time-sensitive whatever its mark; an item marked superseded by hand counts at the recency weight on any rule.
Re-evaluating and verdict versions
Every result's run records the knowledge base kb_revision it was judged at and the prompts it used. GET /api/v1/verdicts/{id}/changes lists what changed since. Re-evaluating (investigate with fill_gaps: 0, the “Re-evaluate against current knowledge” button) judges the same claims again against the knowledge base as it is now, without research; publishing it adds a version whose diff says, claim by claim, label and probability before → after, which indicators changed, and which evidence items were added or changed status (by id). The version list (versions[], and the verdict page) shows each version's kb_revision and diff lines.
curl -s http://127.0.0.1:8787/api/v1/verdicts/b41c…/changes
curl -s http://127.0.0.1:8787/api/v1/verdicts/b41c…/investigate -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" -d '{"fill_gaps": 0}'
# poll, then publish the job: version 2, with
# "diff": {"lines": ["claim #0 \"…\": very likely true 0.98 → insufficient evidence 0.50; items: e0000042: active → erroneous"], …}
Sites and norms
With domain: "si-building" and a site, a check works as a permit reviewer reads a project description. The site is resolved through the place resolver to a card: the municipality, the parcels, the planning units (zones) with the share of the site's area in each, the protective regimes, the acts in force. The card is stored (a site term whose parents are its zones, membership = share; each zone under its municipality, land use and plan act; its regimes as facts on the site), and the plan's structured rules for those units are fetched from the corpus (Bayesian's own store by default). Without a resolver, a site stored before is used as it is stored. The parser writes the project's claims with the site as subject and the factor vocabulary as predicates (fz, fi, height_m, storeys, setback_boundary_m, use, building_type, …), values with units. Before judging, every claim whose factor has no norm in force, or only a norm in words, is read: the corpus is searched with the domain's queries (provisions of another land use's or unit's chapter are dropped), and one model call per claim reads the provisions against it; a number it quotes from a provision becomes a numeric norm, a reading in words is expert evidence with its caveat. Web research is off for this domain. A site the resolver does not find leaves its claims a NoPlace gap, never researched.
Each such claim's norm: factor, requirement (verbatim), locus (corpus, document, version, unit, paragraph, item, url, cite_as), cite_as (the act with the publications of its base text and amendments, then the article and paragraph: “Odlok o Občinskem prostorskem načrtu Občine Polzela (Uradni list RS, št. 96/11, 60/12, …), 92. člen, 1. odstavek”), url, applies (yes, no, unclear) with applies_reason, value_claimed, value_required (“≤ 35 %”, “≥ 4 m”), status (meets, likely_not, cannot_tell, not_applicable, conditional: the direction of the comparison; how sure it is stays in the label and probability) with status_text (“meets only if: (a) requirement in words, 92. člen, 1. odstavek; (b) consent, 92. člen, 2. odstavek”), conditions (for conditional: each way of the norm the claim does not address, verbatim with its article and paragraph, then the ways the claimed value is outside of, marked not applicable), state (statute, unvalidated, read, approved, corrected) and review (a planner's state, who, when and the note), rule, as_of (the day norms were selected at: the claim's date, else today), assumptions (the version in force, the membership share below 1, the extraction or reading state, the norm's condition the claim does not state, the measurement convention, the regimes over the site, the reader's caveat) and considered (every norm looked at: decisive, overridden by a more specific one, not in force on the day, a draft, about another subject). The most specific norm in force decides per zone (sub-unit, unit, land use, building type, the plan's general provisions, national acts), and zones mix by their shares. A norm read from a provision keeps the ways it can be met (the number, a requirement in words such as “tako, da ni motena sosednja posest”, the neighbours' consent of a later paragraph, a justification under 1.5 m): it is met when any way of each provision is; a claim that fails the number while a way in words stays open is conditional, never likely false. A condition the text states (“Sosedje soglašajo z odmikom 3 m.”) becomes the claim's condition stated:consent:… and meets a way of its kind at the reliability of a report, with the assumption that it is taken as given, not verified. A record the caller adds on the claim (the amend op add_evidence: a cadastre extract giving the neighbours' ridge heights) counts in the comparison against every provision and bears the statement out: the reading then counts in full, and the assumptions name the record. The verdict and check pages show a Norms section. A corpus without structured rules is a normal state, listed in the result's notes (not warnings).
curl -s http://127.0.0.1:8787/api/v1/check -H 'Content-Type: application/json' -H "X-Api-Key: $KEY" \
-d '{"text": "Na parceli 929/669 k.o. Polzela načrtujemo novogradnjo enostanovanjske hiše (faktor zazidanosti 0,26). Objekt bo od meje sosednjih parcel odmaknjen 3 m.",
"domain": "si-building", "site": {"ko": 992, "parcel": "929/669"}}'
The place resolver is Bayesian's own spatial store (spatial/spatial.sqlite next to kb.json, filled by bayesian-spatial fetch from GURS, MNVP, ARSO and DRSV and by bayesian-spatial import luma:…) whenever it exists and BAYESIAN_PLACE_RESOLVER is unset: the share of the site in a unit is the overlap of the parcel with the plan's land-use polygons over the parcel's area. GET /api/v1/sites/resolve returns the card alone:
curl -s 'http://127.0.0.1:8787/api/v1/sites/resolve?parcel=992/929/669' -H "X-Api-Key: $KEY"
# {"municipality": {"code": 173, "name": "Polzela"}, "parcels": [{"ko": 992, "parcel": "929/669", "area_m2": 689.9}],
# "units": [{"code": "PO1", "sub_code": "PO1/85", "land_use": "SS", "share": 0.904, "area_m2": 623.3, "act": {"key": "pa-253", …}, …},
# {"code": "PO1", "sub_code": "PO1/65", "land_use": "ZD", "share": 0.096, …}], "acts": [{"key": "pa-253", …}, …],
# "snapshots": {"parcels": "2026-10-11", "units:nrp": "2026-10-17", …}, "source": "local:…/spatial/spatial.sqlite"}
The corpus store
The texts a check reads live in Bayesian's own store, corpus/corpus.sqlite next to kb.json: acts with their provenance (where they were imported from and the primary records behind them: the PIS or PISRS page, the gazette publications, the municipality's text), versions with their validity (a version of law runs to the day before the next one applies) and status, articles with their numbered paragraphs, annex rows, structured norms with their review state, and a municipality's planning units with the keys its plan names. It is filled by bayesian-corpus import luma:<url> --municipality 173 [--national] [--sites 992/929/669], read-only from Luma Intelligence, idempotent (a second import of the same data changes nothing and journals nothing; one that changed the store is journaled in kb.journal.jsonl as corpus_import). With BAYESIAN_CORPUS unset the server reads it; Luma is never consulted at check time. With no place resolver configured, a site the knowledge base holds from an earlier card (its zones, shares and regimes) is used as stored, with a note in the result; a site it does not hold is a NoPlace gap. The corpus routes above serve the same store.
Languages
Every piece of knowledge is stored in English, and may also exist in one other language, with a flag saying which of the two is the original. Most knowledge is English only. Knowledge whose source is in another language keeps the original beside its English rendering: a provision of a Slovenian plan, the citation of an act, a Slovene headline of a measured item, a quote from a Slovene outlet. As JSON such a text is {"en": …, "lang": "sl", "text": …}: text is the original, lang its ISO 639-1 code, en the English (a plain string is English with no other original, so older fields read unchanged). Ids (predicate keys, term, choice-point and context ids) stay English snake_case. A corpus text whose English is not made yet has en equal to text; the texts that reach a norm rule get theirs in one translation call per sync (corpus.translate_on_sync).
A request is answered in its language: lang on /evaluate, /jobs, /check, /review and the investigate endpoints (an investigation or re-evaluation inherits the one its result was answered in), else the text's own, which the parse finds (parsed.language). run.lang records it. When it is not English, the model is asked for its own prose (glosses, notes, hedges, reasons, descriptions) in English with a rendering in the request language beside it; the English goes into the knowledge base, the rendering into the render cache (renders/<lang>/, keyed by a hash of the English, shared across verdicts), never into the knowledge. Each claim then carries local: {lang, label, shown, literal_label, gloss, reading, assumptions, alternatives, flags, norm} in the request language, and the evaluation summary_local.
Fixed words (labels, flags, statuses, gap reasons, assumption words, norm statuses, page strings) come from the vocabulary (data/i18n/<lang>.toml; English and Slovene are complete), never from a model. Each predicate key gets an English reading once (predicate_glosses in the knowledge base, journaled with tool i18n; never used for matching) and readings in other languages in the render cache. Pages: the verdict and check pages show labels, statuses, the assumption table and the norm block in the viewer's language (?lang=, the selector in the header kept in this browser, Accept-Language, else the verdict's own language); prose comes from the render, with a small “original: sl” marker that toggles to the original; quotes and measured items' titles show the original first. Provisions are shown verbatim to a Slovene reader and as their English rendering, marked “original: sl”, to an English one; a reader of a third language sees the original with the English beside it.
Output
An evaluation (the response of evaluate, result of a job, result of a verdict) has these fields:
| Field | Meaning |
|---|---|
summary | Number of claims per label, e.g. {"likely true": 12, "insufficient evidence": 30}. |
stats | claims, by_kind, by_label, framed, framed_unanimous, uncorroborated, verified, disputed_values, decided_facts (facts a rule decided; never a claim about the text itself), mean_probability_decided_facts. |
claims[] | One entry per claim: text, subject, predicate, positive, kind (fact, attribution, evaluation, prediction, insinuation, sentiment, hypothetical), self_reference (true for a claim about the text itself: its sections, what its author does in it; see below), force, label, literal_label, framing {kind, reason, standard, standard_label, agreement}, probability, opinion [belief, disbelief, uncertainty], shown {text, verified, basis}, assumptions and alternatives (premises and contexts), conditions, time, rules, indicators, flags, params. |
context, premises | The context the claims were judged in, when not the default; the premises the text states (not claims). |
chains[] | chains[i] belongs to claims[i]: query (subject, predicate, context, time), arguments[] (every rule considered: status decisive / supporting / overridden / inapplicable, head, strength, independent_sources, dependent_groups, missing, branch, timeless (the rule's proposition does not change with the calendar: newer items do not supersede older ones and its readings are weighed with the frequency model; see Recency), and evidence[] with source, kind, supports, outlet, url, group, published, measured_value ± tolerance unit and fit, record_value, null_result, superseded_by (the strictly newer item on the other side that counts this one at recency.superseded_weight; never on a timeless rule), witness, id, added, by, prompt, and when not active status, status_reason, status_by, status_at), derived_from (claims of the same text it depends on), own_opinion, standard (the chain of a framed claim's standard characterisation). |
warnings, gaps, filled | Parts of the text that could not be parsed or claims a check could not repair; what the knowledge base still needs; what each fill did ("subject predicate: outcome", one line per grouped session, e.g. "janez_jansa [4 indicators of '…']: research in one session: …"), ending with why the rounds stopped ("stopped: budget exhausted …", "stopped: no new gaps …"). |
framing_checked, setting | Whether the framing review ran; context terms that apply to the whole text. |
run | mode (fast, verified, investigation or re-evaluation), thorough, check_framing, fill_gaps, evaluated_at, seconds, model, params_version, params_fingerprint, params_source, engine (build), kb_revision (the knowledge base revision the claims were judged at), prompts (the prompt templates used, name → version), context (default, a context id, text_own or the explicit assumptions) and lang (the language the request was answered in). |
sessions_used | Web-research sessions the pass used, of its fill_gaps; the rest went back to the daily budget. Absent in results stored before it was kept. |
job_id | The job that produced it. Pass it to POST /api/v1/verdicts. |
A verdict adds id, title, text, commissioned_by, published, job_id, source, version, latest_version, versions, investigated_job (for a later version: the job it investigated further) and, for pre-run samples, sample. It is stored as verdicts/{id}.json, next to kb.json by default; version n (2 and later) as verdicts/{id}.versions/{n}.json.
What is kept where
| Where | What | For how long |
|---|---|---|
verdicts/{id}.jsonnext to kb.json; BAYESIAN_VERDICTS_DIR | Published verdicts: text, claims, evidence chains, run, commissioner, and the parse the claims came from (not shown by the API; an investigation starts from it). Later versions in verdicts/{id}.versions/{n}.json. | Permanent. Each version is written once, never replaced or withdrawn; a new version is a new file beside the earlier ones. |
jobs/{id}.jsonnext to kb.json; BAYESIAN_JOBS_DIR | Every job: the text, options, kind and parent, status, timings, the key or address that started it, the result (with its parse) once done, the verdict id once published. A small file is written when the job is queued and replaced when it finishes; the server reads them all back when it starts. | BAYESIAN_JOBS_TTL_HOURS from creation (720: 30 days), at most BAYESIAN_JOBS_MAX (2000; the oldest finished ones go first), on disk as in memory. Unfinished jobs come back as interrupted. Published verdicts are permanent whatever these say; the check page's “All checks on this server” lists the kept checks and the published verdicts. |
kb.json | The knowledge base: terms, rules, sources and evidence that evaluations read and research adds to, each with its provenance and status, and the revision it is at. Shared with the MCP server. | Permanent; grows with research. |
kb.journal.jsonlnext to kb.json; BAYESIAN_JOURNAL | Every change to the knowledge base, one line each, with who made it and why (journal). | Permanent, append-only; past 1 GB (BAYESIAN_JOURNAL_MAX_BYTES) the oldest part moves into gzip archives beside it, never deleted. |
corpus/corpus.sqlitenext to kb.json; BAYESIAN_CORPUS_STORE | Bayesian's own corpus: acts with provenance, versions, units with a full-text index, structured norms, planning units, one row per import that changed something. | Permanent; an import replaces a version's units or norms only when they changed, and never wipes them on an empty answer. |
renders/<lang>/next to kb.json; BAYESIAN_RENDERS_DIR | Renderings of English knowledge text in other languages (made at generation time or by a render call), one file per text, and readings of predicate keys. A version's render is kept beside it, verdicts/{id}.versions/{n}.render.{lang}.json. | A cache: kept, shared across verdicts, never journaled (a rendering never changes a verdict). |
| Server memory | Rate limits and the daily research budget. Jobs are also held in memory, as the fast path. | Until the server restarts. |
Your browser (localStorage) | The API key you entered, the language picked in the header, and the ids and first 80 characters of the checks this browser started (the “Recent checks” list on /check, the last 50). | Until you clear the site's data. |
API key and budget
- Set
BAYESIAN_API_KEYS=key1,key2to requireX-Api-Keyon the endpoints marked “yes”. If the variable is unset, the server is open. Reading verdicts, stats and health never needs a key: verdicts are public. fill_gapscounts web-research sessions. A verified pass runs in rounds (at most 4): research, judge again, research what the new verdicts still need. One session researches one gap, measures one indicator, or researches up to 4 gaps about one subject at once (the indicators of a definition, typically; more take further sessions), and each counts once. A grouped session getsBAYESIAN_RESEARCH_TIMEOUT(360 s) once per two gaps, at least twice. One that times out returns nothing (claude -panswers only when done) and is retried once as two sessions on the halves of its gaps, side by side, each with its own allowance (“… [4 predicates]: grouped research timed out after 720s, nothing returned; retried as two sessions of 2 and 2”, then a line per half); the retry is not counted again. A half that times out too leaves its gaps open for the next pass. Proposing a definition for an evaluation (one model call, no web, at most 4 per pass) and term cards for unknown words are not counted. A claim about the text itself (self_reference: “This paper is divided into three sections”, “Section two addresses …”, the author's “I am concerned with …”) is never researched: the text settles it, so it opens no gap, is judged on no evidence (insufficient evidence, with the flag “about the text itself: not checked against outside sources”) and is not a decided fact. The parser marks such claims; a parse stored before the flag is read by its subject id (this_paper…,this_article…,the_text,the_author,essay_author,speaker_…_author,…_paper_section_…), and gaps logged under such ids earlier are not planned. The rounds stop when the sessions are used up, when a round added nothing, or when no gap is left to try; the last line offilledsays which. The check page asks for 5 by default and offers 10, 20 and 50; the open gaps of any result can then be investigated further.- Research is the expensive part, so each key gets a daily budget of
fill_gaps:BAYESIAN_FILL_BUDGET, 200 per UTC day by default. A request (a check or an investigation) may ask for at mostBAYESIAN_WEB_MAX_FILL(50). A job is charged itsfill_gapswhen it starts; when it finishes, the sessions it did not use are credited back to the same UTC day's budget (not after midnight), and its result says how many it used (sessions_used). A failed job still counts in full. The count is kept in memory: it resets when the server restarts. Without keys, the budget applies per client address. Over the budget the server answers 429. - Limits: request bodies up to 256 KB, texts up to 20,000 characters (
BAYESIAN_WEB_MAX_CHARS), 120 API requests per minute per client (BAYESIAN_WEB_RATE), 10 evaluations started per minute (BAYESIAN_WEB_EVAL_RATE), 2 evaluations at once (BAYESIAN_WEB_CONCURRENCY), and 16 queued or running (BAYESIAN_WEB_MAX_PENDING).
Errors
Every error is {"error": "…"} with one of these statuses: 400 bad request (also a bad limit or status in the job list; an amendment without a reason or with an unknown target; an unknown context, choice point or value), 401 missing or unknown key, 403 an amendment of items another key wrote (with forbidden), 404 unknown job, verdict or version, 409 job not finished or interrupted (for an investigation also: no open gaps, or no stored parse), 413 text too long, 429 rate or budget exceeded, 502 evaluation failed, 503 too many pending evaluations, 504 sync timeout (with job_id).
Same engine over MCP
bayesian-mcp kb.json exposes the same pipeline to Claude and other MCP clients as evaluate_statement, explain, fill_gaps, investigate (with the parsed object of a verbose evaluation), audit (with the views by_actor and status), kb_stats, history, set_status, retract_evidence, retract_rule, restore, purge, set_norm_state (a planner's review of a read norm), update_trust, add_choice_point, list_choice_points, set_choice_default, add_context, list_contexts, review_text, supply_facts (facts and questions), first_principles (reasoning from first principles) and the other tools (assumes on add_evidence, define_predicate and accept_definition; context on evaluate_statement and evaluate_claim; MCP is local and may amend anything). evaluate_statement takes domain and site as /check does, and evaluate_claim takes domain. Both servers share kb.json, so what one learns the other uses. The corpus is synced from the command line with bayesian-corpus sync, bayesian-corpus as-of 2024-03-01 --site 992/929/669 prints the norms in force at a site on a day, and bayesian-corpus read --municipality 173 [--factors …] [--land-uses …] reads a plan's norms ahead of checks (one model call per land-use chapter and factor with provisions not yet read).
Knowledge map
Three read-only endpoints behind the map. They draw on the knowledge base as it is now; nothing is evaluated or written. With BAYESIAN_API_KEYS set, the graph and the term card need X-Api-Key, as the job endpoints do; a published verdict's neighbourhood is open, like the verdict. The map is computed on the first request after kb.json changed and kept in the server's memory, so these answer from memory; revision names the state of kb.json they describe, and the Server-Timing header says how long the answer took.
| Endpoint | Key | What it does |
|---|---|---|
| GET/api/v1/graph | yes | The terms as a graph: {revision, computed_at, collapsed, expanded, nodes, edges, communities, outlets, alias_candidates, stats}. Query: layers (comma-separated: rules (default), evidence, taxonomy, definitions, gaps; all; none for nodes only; alias candidates come with any set but none), community (an id or several: in bubbles mode the communities shown in full, otherwise a filter), q (search over ids, names and glosses: the matches, marked matched, and their neighbours), min_weight (default 1), limit_nodes (default 2000: with more nodes than this, every community not selected becomes one bubble node of kind community, orphans one more, and the edges between them are summed per pair, kind aggregate, with kinds), collapse (true or false forces bubbles or detail), limit_outlets (default 60). |
| GET/api/v1/graph/term/{id} | yes | The term card: node, rules about it (id, head, predicate, polarity, probability, label, opinion, independent sources, evidence items, flags: contested, single source, superseded items, stale measurement, collective evidence only, public act), mentioned_in (rules naming it as object or condition, at most 50, and mentioned_total), gaps, definitions with their indicators, neighbours, outlets, community, verdicts (published verdicts, latest versions, with claims whose subject or object it is) and history (journal events, once the knowledge base keeps a journal; empty until then). 404 for an unknown term. |
| GET/api/v1/graph/verdict/{id} | verdict: no; job: yes | The subgraph a verdict used, in the same node and edge schema: its claims' subjects and objects (nodes with claims, the indices of the claims about them; claim edges), the rules in its evidence chains (shared_rule edges with the rule ids and the rule's status in the chain, decisive first) and the outlets of their evidence (nodes of kind outlet, evidence edges). Term nodes carry their community and health from the map; a term the knowledge base no longer has is missing. {id} is a verdict id (?version=n for an earlier version) or a job id (its result, before publishing; visible to the job's key only); source says which. |
Nodes and edges
- Node:
id,label(a display name),gloss,kind(individual,class,context,predicate,community,outlet),layer,parents,rules(rules whose subject it is),mentions(rules naming it as object or condition),orphan,health(contested,single_source,stale,superseded,collective_only,gaps,definitions: counts over its rules),status(contested,single_source,staleorhealthy),community,degree; predicate nodes addsubject,predicate,roles(evaluative,indicator,alias) andprobability; bubbles addsize. - Edge:
source,target,kind(shared_rule,shared_evidence,taxonomy,definition,indicator,predicate,alias_candidate,gap, and in a verdict's neighbourhoodevidenceandclaim;aggregatebetween bubbles),weight,layer, and what makes it:rules(rule ids),evidence(<rule id>#<item>),outlets,gaps,more(ids left out of those lists, at most 12 each). - Community:
id(0 is the largest),label(its two or three terms in the most rules),size,top_terms,rules,evidence,health(members with each flag),outlets(the five feeding it most) andfed_bywhen one outlet, wire agency or repeated document supplies half its evidence or more. Communities come from Louvain modularity on the rule ties alone, in a fixed order, so the same knowledge base gives the same communities. - Tenancy:
layerisbasefor everything in the knowledge base; add-on layers will bepack:<id>andtenant:<id>, and an edge where one contradicts the base will carryconflict: true.
# the map as the page asks for it (bubbles above 400 nodes)
curl -s 'http://127.0.0.1:8787/api/v1/graph?limit_nodes=400'
# one community in full, with shared outlets and gaps
curl -s 'http://127.0.0.1:8787/api/v1/graph?layers=rules,evidence,gaps&community=3'
# a term card, and the neighbourhood of a verdict
curl -s http://127.0.0.1:8787/api/v1/graph/term/janez_jansa
curl -s http://127.0.0.1:8787/api/v1/graph/verdict/sample-ljubljana-column