The substrate
contradiction-preserving · evidence-first

donto API reference

donto is a contradiction-preserving CLAIM/DISCOVERY substrate: a bitemporal, paraconsistent, evidence-first knowledge base that holds incompatible claims forever as legal state, anchors each to its source, links them with typed argument edges, and re-ranks by reality over time instead of deleting on conflict. This document is the developer-facing reference for donto's programmatic surfaces — every HTTP API you can use to read, write, and query the substrate, plus its MCP (Model Context Protocol) tools. It covers five services (a memory API, a distributed embedding coordinator, a genealogy extraction/ingest API, the core substrate sidecar, and a read-only admin console), the authentication model, the shared bitemporal data model, and end-to-end quickstart recipes.


Overview

Service Public base URL Purpose Auth
donto-memory https://memories.apexpots.com Agentic episodic + semantic memory: memorize, recall, substrate-wide search, module ingest, reconsolidation, operator observability. Public for the agent contract; operator routes (/jobs/*, /explore/*) gated by an optional bearer token.
donto-embed-coordinator https://donto.org/embed (also https://memories.apexpots.com/embed) Distributed embedding work queue: enqueue items, lease batches, submit computed vectors. Lets spare machines/GPUs embed at billion scale. Bearer token on every endpoint except /embed/health.
donto-api (genealogy) https://genes.apexpots.com LLM extraction + ingest into the knowledge graph, plus rich read/search/graph/alignment endpoints over the substrate. Public (no auth).
dontosrv (core substrate) https://genes.apexpots.com (internal localhost:7879) The core bitemporal claim substrate HTTP sidecar: ~70 endpoints for the claim write-path, read/search, evidence, trust kernel, arguments, alignment, shapes/rules, and schema discovery. Public reads; writes ungated at the HTTP layer (policy gates applied at the SQL layer per context/action).
donto-admin https://admin.donto.org Read-only observability console: substrate metrics, contexts, fabric, processes, ingestion jobs, per-job charts/nebula/panels, relation introspection, BEAM progress. Read-only Postgres role (donto_ro); no per-request credentials.

donto-memory-mcp is not a separate HTTP service — it is the MCP front-end over donto-memory's /recall, /search, and /memorize endpoints. See MCP access.


Authentication

donto's programmatic surfaces use three distinct auth postures. There is no per-request API key for the public substrate — the substrate is openly readable and (for the genealogy/substrate write-path) openly writable by design.

The token model

  1. Public endpoints (no auth). Most of the agent contract is public: donto-memory's /health, /version, /modules, /memorize, /recall, /search, /search/resources, /ingest, /reconsolidate/*; all of donto-api; all of dontosrv reads/writes. Just send the request.

  2. Bearer token (embed coordinator + optional memory ops).

    • donto-embed-coordinator requires Authorization: Bearer <TOKEN> on every endpoint except /embed/health. The token is read from the DONTO_EMBED_TOKEN environment variable, which is sourced from the secret file /etc/donto/embed-coordinator.token (server-side; never expose it). A missing or invalid token returns 401 Unauthorized. If the env var is unset, the service runs open (dev mode only).
    • donto-memory operator routes (/jobs, /jobs/list.json, /jobs/:id, /jobs/:id/raw, /explore, /explore/*.json) require Authorization: Bearer <TOKEN> only if DONTO_MEMORY_OPS_TOKEN is set on the server. If it is unset, these routes are a pass-through (local dev). The /api endpoint's ops_token_required field tells you which mode is live.
  3. Read-only Postgres role (admin console). donto-admin requires no per-request credentials; access is enforced at the database level. It connects with the donto_ro role (via the server-side DONTO_RO_DSN env var), which is locked to SELECT-only with default_transaction_read_only=on.

Where keys live (by reference, never inline)

Secret Server-side location Used by
Embed coordinator bearer token /etc/donto/embed-coordinator.token (env DONTO_EMBED_TOKEN, from /etc/donto/embed-coordinator.env) donto-embed-coordinator
Memory ops token env DONTO_MEMORY_OPS_TOKEN (set on the donto-memory server if gating is enabled) donto-memory operator routes
Read-only DSN env DONTO_RO_DSN (defaults to a [email protected]:5432/donto connection string) donto-admin

Never paste the literal secret values into requests, configs, or logs. Pass tokens only as Authorization: Bearer <TOKEN>, substituting the real value from the secret store at runtime.

Passing tokens

Authorization: Bearer <TOKEN>

The Cloudflare User-Agent gotcha

All public domains are proxied through Cloudflare. Cloudflare blocks some default client User-Agents (e.g. plain python-urllib) with a 403 / Cloudflare error 1010. Set a non-default User-Agent such as curl/8curl, reqwest, and aiohttp pass by default, but if you build raw requests, add a User-Agent header:

curl -A 'curl/8' https://donto.org/embed/health

This is required against donto.org / memories.apexpots.com / genes.apexpots.com whenever your client library would otherwise send a blocked UA.


Conventions

Base URLs

Use the public base URLs from the Overview table. Internal ports (:7900, :7930, :7879, :8000, :3103, :8233) are reachable only on the host and are noted for operators; external developers use the HTTPS domains.

Content type

All request bodies and responses are JSON unless noted. Send Content-Type: application/json on POSTs with a body. A handful of endpoints return other media types (text/plain for /metrics, /health on dontosrv, the DontoQL grammar; text/markdown for /agent.md; HTML for /jobs, /explore, /docs; Server-Sent Events for /firehose/stream) — these are called out per endpoint.

The bitemporal / context data model

A caller must understand a few substrate concepts that recur in every payload:

  • Statement (claim). The atomic unit: subject predicate object in a context. Objects are either an IRI (object_iri) or a literal (object_lit, an object {v: value, dt: xsd-type, lang?: language} — e.g. {"v":"1860-01-15","dt":"xsd:date"}). Subject and object exactly one of IRI/literal.
  • IRIs. Subjects are minted ex:kebab-case (e.g. ex:mary-watson). Predicates are camelCase (e.g. bornIn, marriedTo). Contexts are ctx:namespace/topic/source (e.g. ctx:genes/mary-watson/obituary) or donto:... / mem:... namespaced IRIs. Memory holders are IRIs like agent:my-bot or user:alice; sessions like discord:user:12345. Predicates are freely minted by extractors (abundance) — variants are reconciled at query time by the alignment engine, not by a fixed schema.
  • Contexts. Named scopes (sources, extraction runs, research questions). Facts are scoped per context, and contexts can carry policies. Examples: ctx:genes/<topic>/<source>, ctx:extract/<file>/<model>, mem:module/episodic.
  • Bitemporality. Every statement carries two time axes:
    • transaction time (tx_time, exposed as tx_lo/tx_hi): when the system recorded / retracted the fact. An open interval (tx_hi/upper(tx_time) is null) means the fact is currently believed; a closed interval means it was retracted (soft-deleted, never destroyed).
    • valid time (valid_from/valid_to, exposed as valid_lo/valid_hi): when the fact became / ceased being true in the world. Distinct from when it was recorded.
    • Time-travel: read endpoints accept as_of_tx (RFC3339) to ask "what did we believe on this past date?" and as_of_valid (date) for valid-time slicing.
  • Polarity. Statements are asserted | negated | absent | unknown. (In raw flags, the low 2 bits encode polarity: 0=asserted, 1=negated, 2=absent, 3=unknown.)
  • Maturity (the E0–E4 / 0–4 ladder). 0=L0 raw, 1=L1 registered, 2=L2 evidenced, 3=L3 validated, 4=L4 certified. Higher maturity is prioritized in queries. (Admin charts may show buckets 0–7.)
  • Evidence anchoring. Facts link to sources via donto_evidence_link → donto_span → donto_document_revision → blob. A span carries surface_text (the anchored snippet) plus character offsets. Extraction and anchoring are separate stages; anchorless facts are held honestly rather than given bogus spans.
  • Identity-as-hypothesis. Subjects may be the "same" entity under an identity lens (lens_name); resolution happens at query time, not by destructive merge.

Error shape

Errors vary slightly by service:

  • dontosrv / donto-api / donto-embed-coordinator: a JSON object with an error field, e.g. { "error": "..." }, possibly with detail / reason.
  • donto-admin: a uniform envelope { "ok": false, "error": string, "at": ISO8601 }, HTTP 500 unless noted. Successful admin responses are { "ok": true, "data": T, "at": ISO8601 }, never cached (cache-control: no-store).
  • HTTP statuses: 400 bad input (empty query, missing/invalid fields, unknown module), 401 missing/invalid token (embed coordinator, gated memory ops), 404 not found (record, job, revision, unknown module/panel), 500 substrate/DB/extraction failure, 202 async deferred (extraction queued). Search endpoints degrade gracefully: on a 9s timeout they return partial: true with partial results instead of failing.

donto-memory

https://memories.apexpots.com — an agentic episodic + semantic memory substrate. It stores raw text (episodic) alongside LLM-extracted structured claims (semantic) into the bitemporal substrate, scoped by holder (agent IRI, for recall isolation) and optionally session. Memory is composed over mem:module/* contexts and stored as donto_statement rows.

Extraction modes: single (one LLM call, ~20–100 facts), exhaustive (five parallel apertures, ~80–250 facts, ~5× token cost), deep (N sequential passes, default 3, max 10, dedup at end). The default mode is the server's DONTO_MEMORY_EXTRACT_MODE (defaults to exhaustive). Slow modes (deep, exhaustive) return 202 Accepted and run on a Temporal durable queue (with a tokio fallback); fast modes return 200.

OpenAPI spec: https://memories.apexpots.com/openapi.json. Swagger UI: https://memories.apexpots.com/docs.

Caveats (live-verified 2026-06-07): POST /search and POST /search/resources are served and return real data, but are absent from the published openapi.json — don't rely on the spec alone to discover them (they're documented below). Operator routes (/jobs/*, /explore/*) are gated only when DONTO_MEMORY_OPS_TOKEN is set; the currently deployed instance reports ops_token_required: false (ungated), so those routes answer without a token right now — check GET /api for the live mode rather than assuming a 401.

GET /health

Health check. Auth: none.

  • Request: no parameters.
  • Response: {status: string}{"status":"ok"} if running.
curl https://memories.apexpots.com/health

GET /version

Service version and configuration. Auth: none.

  • Request: no parameters.
  • Response: {service: string, version: string, substrate_url: string, substrate_contract_floor: string}.
curl https://memories.apexpots.com/version

GET /substrate

Substrate health + contract details. Auth: none.

  • Request: no parameters.
  • Response: {contract: {actions: [...], ...}, health: {...}, substrate_contract_floor: string}. Runs contract_version() and substrate_health() in parallel with an 8s timeout; health degrades gracefully if substrate counts are slow.
curl https://memories.apexpots.com/substrate

GET /api

API summary and endpoint listing. Auth: none.

  • Request: no parameters.
  • Response: {service, version, substrate_contract_floor, ops_token_required: boolean, async_memorize_queue?: {pending, completed_24h, failed_24h, lost_24h, drain_safe}, endpoints: {agent_contract: string[], documentation: string[], operator: string[]}}. ops_token_required tells you whether operator routes are gated.
curl https://memories.apexpots.com/api

GET /modules

List memory modules. Auth: none.

  • Request: no parameters.
  • Response: {modules: [{module_iri, label, description, form, function, version, source, enabled_in_db?: boolean}]}. Standard modules: mem:module/episodic, mem:module/semantic-claim, mem:module/preference.
curl https://memories.apexpots.com/modules

POST /memorize

Save an episodic chunk + LLM-extracted semantic claims. Auth: none.

  • Request body:
    • holder (string, required) — agent IRI scope, e.g. agent:my-bot.
    • session_id (string, optional) — e.g. discord:user:12345.
    • text (string) — the chunk (required unless images provided).
    • modality (string, optional, default model_output).
    • extract (boolean, optional, default true) — extract semantic claims via LLM.
    • mode (string, optional) — single | exhaustive | deep.
    • passes (u32, optional) — for mode=deep (1–10, default 3).
    • async (boolean, optional) — force deferred execution.
    • images (string[], optional) — http(s) URLs or data:image/...;base64; OCR transcription is prepended to episodic text when enabled.
    • facts (ExtractedFact[], optional) — pre-extracted facts from upstream.
    • valid_from (string, optional) — YYYY-MM-DD or RFC3339; when the fact became true in the world (distinct from tx_time).
  • Response (200 sync, or 202 async): {holder, session_id, episodic_record_id: uuid, episodic_record_iri, extracted: boolean, extract_mode, facts_extracted, facts_ingested, dedup_collisions, semantic_record_ids: uuid[], model?, usage?: {completion_tokens, input_tokens, output_tokens}, aperture_yields: [], facts: ExtractedFact[], elapsed_ms, warnings: string[], queue_id?: uuid}. On 202: {status:"queued", queue_id, durable: boolean, holder, session_id, extract_mode, passes, note}.
  • Behavior: async (202) when mode is deep/exhaustive/opencode or async=true; else sync (200). Poll /jobs or /recall for async results.
curl -X POST https://memories.apexpots.com/memorize \
  -H 'Content-Type: application/json' \
  -d '{"holder":"agent:my-bot","session_id":"discord:user:12345","text":"I met Annie Davis in 1979."}'

POST /memorize/batch

Bulk memorize. Auth: none.

  • Request body: {items: MemorizeReq[]} — an array of /memorize request bodies. Processed sequentially.
  • Response: {results: object[]} — each is a MemorizeResp (same fields as /memorize) or {error, holder, text_preview}.
curl -X POST https://memories.apexpots.com/memorize/batch \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"holder":"agent:my-bot","text":"First memory"},{"holder":"agent:my-bot","text":"Second memory"}]}'

POST /recall

Holder-scoped episodic + semantic recall bundle (hybrid lexical + semantic, bitemporal). Auth: none.

  • Request body:
    • holder (string, required) — scope isolation.
    • query (string, optional) — hybrid search term.
    • limit (number, optional, default 50; clamped 1–configured max).
    • session_id (string, optional).
    • subject / predicate / object_iri (string, optional) — exact IRI filters.
    • action (string, optional, default read_content) — policy action.
    • polarity (string, optional, default asserted).
    • as_of_tx (RFC3339, optional) — bitemporal time-travel.
    • as_of_valid (date, optional) — valid-time slice.
    • lens_name (string, optional) — identity hypothesis.
    • min_maturity (0–4, optional).
    • module_iris (string[], optional).
    • permitted_only (boolean, optional, default true).
  • Response: {holder, action, modules_used: string[], row_count, rows: [{statement_id, subject, predicate, object_iri, object_lit: {v, dt, lang?}, context, flags, maturity, valid_from, valid_to, as_of, provenance}], policy_report: {default_action, permitted_only}, lens?: string, as_of?: string}. Composed over mem:module/* contexts; the policy report is always included.
curl -X POST https://memories.apexpots.com/recall \
  -H 'Content-Type: application/json' \
  -d '{"holder":"agent:my-bot","limit":10}'

POST /search

Substrate-wide ranked full-text search over names. Auth: none (intentionally public).

  • Request body:
    • query (string, required, non-empty) — lexemes are ANDed.
    • context_prefix (string, optional) — scope filter, LIKE '%prefix%' (e.g. ctx:genes).
    • limit (number, optional, default 50; clamped 1–500).
  • Response: {query, context_prefix, row_count, rows: [{statement_id, subject, predicate, object_iri, object_lit, context, score}], partial: boolean, elapsed_ms, note?}. Searches IRI path segments, rdfs:label literals, and short literal prefixes across the entire substrate (≈39M rows), backed by the donto_statement_fts_name GIN index. Candidate set capped at 2000 rows; partial=true if the 9s timeout is exceeded; score is ts_rank.
curl -X POST https://memories.apexpots.com/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"love","limit":3}'

POST /search/resources

Full-text search over resource source bodies. Auth: none.

  • Request body: {query: string (required, non-empty), limit?: number (default 20, clamped 1–200)}.
  • Response: {query, row_count, rows: [{document_id, revision_id, score, snippet, blob_uri, body_uri}], partial: boolean, elapsed_ms, note?}. Searches donto_document_revision.body (raw source text); returns the latest revision per document, a highlighted snippet (max ~300 chars, ts_headline MaxFragments=1/MaxWords=30), blob_uri (gs://apex-494316-donto/sha256/<hash> for stored blobs) and body_uri (file:// legacy). Candidate cap 300 rows.
curl -X POST https://memories.apexpots.com/search/resources \
  -H 'Content-Type: application/json' \
  -d '{"query":"love","limit":3}'

POST /ingest/:module_iri

Direct module ingest (bypass LLM extraction). Auth: none.

  • Path param: module_irimem:module/episodic (text), mem:module/semantic-claim (structured claims), mem:module/preference (key-value). Short names are canonicalized (episodicmem:module/episodic).
  • Request body: IngestInput {holder?, session_id?, ...module-specific fields}.
  • Response: {record_id: uuid, record_iri, module_iri, anchored_to: {statement_id: uuid, frame_id: uuid, context_iri}}. 404 if module not registered, 400 for invalid input.
curl -X POST 'https://memories.apexpots.com/ingest/mem:module/episodic' \
  -H 'Content-Type: application/json' \
  -d '{"holder":"agent:my-bot","session_id":"test:1","text":"Raw episodic chunk"}'

POST /reconsolidate/enqueue

Enqueue a record for reconsolidation. Auth: none.

  • Request body: {record_id: uuid (required), reason?: string (default explicit), priority?: f64}. Higher priority is processed first (default 0.0).
  • Response: {queue_id: uuid, record_id, reason, priority, available_at: RFC3339}. 404 if record not found. Adds to donto_x_memory_reconsolidation_queue with a coalesce window.
curl -X POST https://memories.apexpots.com/reconsolidate/enqueue \
  -H 'Content-Type: application/json' \
  -d '{"record_id":"<uuid>","reason":"explicit","priority":1.0}'

GET /reconsolidate/queue

List the reconsolidation queue. Auth: none.

  • Query params: limit? (default 100, clamped 1–1000).
  • Response: {count, items: [{queue_id, record_id, reason, priority, available_at, claimed_at?, claimed_by?, completed_at?}]}. Shows pending entries (completed_at IS NULL), sorted by priority DESC, available_at ASC.
curl 'https://memories.apexpots.com/reconsolidate/queue?limit=5'

GET /jobs

Jobs list (HTML view). Auth: bearer token if ops_token_required=true.

  • Query params: endpoint? (ilike filter), holder? (exact), limit? (default 100, clamped 1–1000).
  • Response: 200 OK HTML with a filterable audit table (from donto_x_memory_job_log).
curl -H 'Authorization: Bearer <TOKEN>' https://memories.apexpots.com/jobs

GET /jobs/list.json

Jobs list (JSON). Auth: bearer token if ops_token_required=true.

  • Query params: endpoint?, holder?, limit? (default 100, clamped 1–1000).
  • Response: {count, jobs: [{job_id, created_at, endpoint, holder, session_id, status_code, elapsed_ms, facts_extracted?, facts_ingested?, rows_returned?, model?, total_tokens?, error?}]}.
curl -H 'Authorization: Bearer <TOKEN>' https://memories.apexpots.com/jobs/list.json

GET /jobs/:id

Job detail (HTML). Auth: bearer token if ops_token_required=true.

  • Path param: id (uuid).
  • Response: 200 OK HTML with the full job detail (request body, response body, metrics, elapsed time), or 404.
curl -H 'Authorization: Bearer <TOKEN>' \
  https://memories.apexpots.com/jobs/550e8400-e29b-41d4-a716-446655440000

GET /jobs/:id/raw

Job detail (JSON). Auth: bearer token if ops_token_required=true.

  • Path param: id (uuid).
  • Response: JobDetail {job_id, created_at, endpoint, holder, session_id, status_code, elapsed_ms, request, response, facts_extracted?, facts_ingested?, rows_returned?, model?, prompt_tokens?, completion_tokens?, total_tokens?, error?}, or 404.
curl -H 'Authorization: Bearer <TOKEN>' \
  https://memories.apexpots.com/jobs/550e8400-e29b-41d4-a716-446655440000/raw

GET /explore

Interactive memory explorer (HTML). Auth: bearer token if ops_token_required=true.

  • Request: no parameters.
  • Response: 200 OK self-contained HTML/JS explorer that drills holder → sessions → records → facts via the /explore/*.json endpoints below.
curl -H 'Authorization: Bearer <TOKEN>' https://memories.apexpots.com/explore

GET /explore/stats.json

Memory system statistics. Auth: bearer token if ops_token_required=true.

  • Response: {holders, sessions, records, modules, recall_events}.
curl -H 'Authorization: Bearer <TOKEN>' https://memories.apexpots.com/explore/stats.json

GET /explore/holders.json

List memory holders. Auth: bearer token if ops_token_required=true.

  • Query params: q? (ilike match), limit? (default 100, clamped 1–1000).
  • Response: {count, holders: [{holder, records, sessions, first_seen, last_seen}]}, ordered by last_seen DESC.
curl -H 'Authorization: Bearer <TOKEN>' \
  'https://memories.apexpots.com/explore/holders.json?limit=10'

GET /explore/sessions.json

List sessions under a holder. Auth: bearer token if ops_token_required=true.

  • Query params: holder (required), limit? (default 100, clamped 1–1000).
  • Response: {holder, count, sessions: [{session, module_iri, records, first_seen, last_seen}]}. Session (no session) for null session IRIs.
curl -H 'Authorization: Bearer <TOKEN>' \
  'https://memories.apexpots.com/explore/sessions.json?holder=agent%3Amy-bot'

GET /explore/records.json

List records under a holder/session. Auth: bearer token if ops_token_required=true.

  • Query params: holder (required), session?, module?, limit? (default 100, clamped 1–1000).
  • Response: {holder, count, records: [{record_id, record_iri, module_iri, session_iri?, holder_iri?, root_statement?, root_context?, created_at, metadata}]}, ordered by created_at DESC.
curl -H 'Authorization: Bearer <TOKEN>' \
  'https://memories.apexpots.com/explore/records.json?holder=agent%3Amy-bot&session=discord%3Auser%3A12345'

GET /explore/facts.json

List facts (statements) derived from a record or session. Auth: bearer token if ops_token_required=true.

  • Query params: record_iri? (ctx:memory/claim/<uuid> or ctx:memory/episodic/<uuid>), session?, holder? (required when session is set), limit? (default 100, clamped 1–2000).
  • Response: {count, facts: [{statement_id, subject, predicate, object_iri, object_lit, context, flags, polarity, tx_lo}]}. Either record_iri or session is required; holder is required when session is set (cross-holder isolation). flags low 2 bits encode polarity.
curl -H 'Authorization: Bearer <TOKEN>' \
  'https://memories.apexpots.com/explore/facts.json?record_iri=ctx%3Amemory%2Fepisodic%2F550e8400-e29b-41d4-a716-446655440000'

GET /openapi.json

OpenAPI 3.0 specification. Auth: none. Returns the full JSON spec (cached at startup).

curl https://memories.apexpots.com/openapi.json | jq .

Documentation surfaces (auth: none)

  • GET /docs — Swagger UI (HTML), loads /openapi.json.
  • GET /agent.md — integration guide for AI agents (text/markdown).
  • GET /llms.txt — same content at the canonical llms.txt path (text/plain).
  • GET /integration-patterns.md — integration patterns and recipes (text/markdown).
  • GET / — homepage (HTML, with ETag / Cache-Control).
curl https://memories.apexpots.com/agent.md

donto-embed-coordinator

https://donto.org/embed (also https://memories.apexpots.com/embed) — a distributed embedding work queue that offloads CPU-bound bge-small embedding to spare machines and GPUs. A worker is dumb: lease → embed (BAAI/bge-small-en-v1.5, 384 dims) → submit; it never touches the database directly. The queue uses FOR UPDATE SKIP LOCKED for concurrent-safe distribution, with ~15-minute stale-lease reclaim. Three providers ship: memory_chunk, predicate, entity — all 384-dim.

Every endpoint requires Authorization: Bearer <TOKEN> except /embed/health. The model is pinned; do not change it without re-embedding the entire fabric. No OpenAPI spec is exposed. Remember the Cloudflare UA gotcha — add -A 'curl/8'.

GET /embed/health

Health check with provider list. Auth: none.

  • Request: no body.
  • Response: {ok: boolean, targets: string[]} — always {"ok": true, "targets": ["memory_chunk", "predicate", "entity"]}.
curl -A 'curl/8' https://donto.org/embed/health

GET /embed/stats

Embedding queue statistics and per-provider source/embedded counts. Auth: Bearer token.

  • Request: no body / query params.
  • Response: { "<provider>": {source: integer|null, embedded: integer|null, queue: {pending, leased, done}} } for each of memory_chunk, predicate, entity. Source/embedded counts are cached (TTL 300s, configurable via EMBED_COUNT_TTL_S); queue counts are live. 401 if token missing/invalid.
curl -A 'curl/8' -H 'Authorization: Bearer <TOKEN>' https://donto.org/embed/stats

POST /embed/enqueue

Enqueue pending items from a source into the embedding work queue. Auth: Bearer token.

  • Request body: {target: string (required: memory_chunk|predicate|entity), limit?: integer (default 50000)}.
  • Response: {target, enqueued: integer}. Scans the source table for items missing embeddings and inserts up to limit into donto_embed_queue with status='pending'. Idempotent (ON CONFLICT (target,item_id) DO NOTHING). 401 if token missing/invalid; 400 if target unknown.
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"target":"memory_chunk","limit":10000}' \
  https://donto.org/embed/enqueue

POST /embed/lease

Lease embedding items for a worker to compute. Auth: Bearer token.

  • Request body: {worker_id?: string (default anon), n?: integer (default 128, capped 1024), target?: string (filter to one provider)}.
  • Response: {batch: [{target, item_id, text, model, dim}]}. model is BAAI/bge-small-en-v1.5, dim is 384. Atomically leases up to n pending items (or stale leases older than EMBED_LEASE_TTL_MIN=15min), setting status='leased', leased_by, leased_at. Uses FOR UPDATE SKIP LOCKED to avoid double-booking; leases auto-reclaim if not submitted within the TTL.
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"worker_id":"worker-1","n":100,"target":"memory_chunk"}' \
  https://donto.org/embed/lease

POST /embed/submit

Submit computed embeddings and mark the queue items done. Auth: Bearer token.

  • Request body: {items: [{target: string (memory_chunk|predicate|entity), item_id: string, vector: number[]}]}. Vector length must match the provider's dim (384 for all current providers).
  • Response: {upserted: integer}. Upserts vectors into the target table (donto_x_memory_chunk_embedding, donto_predicate_embedding, donto_entity_embedding), sets status='done', records the text sig_hash. Wrong-dim vectors are logged and skipped. Transactional per target.
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"items":[{"target":"memory_chunk","item_id":"abc-123","vector":[0.1,0.2]}]}' \
  https://donto.org/embed/submit

donto-api (genealogy extraction & ingest)

https://genes.apexpots.com — a FastAPI service that extracts knowledge from text via an LLM and ingests it into the knowledge graph, plus rich read/search/graph/alignment endpoints over the substrate (it talks to dontosrv internally). The graph is bitemporal and paraconsistent: facts carry valid-time and transaction-time, support historical as-of queries, and are soft-deleted via bitemporal closure. Predicates are open-world minted by the LLM; alignment solves vocabulary variance. Maturity levels 0–4 track quality. No auth (the operator may add bearer-token enforcement later). OpenAPI: https://genes.apexpots.com/openapi.json (authoritative for signatures).

POST /extract-and-ingest

Extract knowledge from text and ingest into the graph (primary agent endpoint). Auth: none.

  • Request body: text (string, required) — source document; context (string, required) — context IRI, e.g. ctx:genes/mary-watson/obituary; model (string, optional, default glm) — LLM shortcut (glm=GLM-5, grok, mistral) or full OpenRouter ID; mode (string, optional, default opencode) — opencode (GLM-5.1 agent) | exhaustive | fast; budget_usd (number, optional) — per-call USD cap.
  • Response: {model, context, mode, facts_extracted, statements_ingested, anchors_attached, yield (facts/char), elapsed_ms, timing: {...}, usage: {...}}.
  • Notes: synchronous; 10-minute timeout. ~60–150 facts per 500-word article. Idempotent (content-hash dedup).
curl -X POST https://genes.apexpots.com/extract-and-ingest \
  -H 'Content-Type: application/json' \
  -d '{"text": "Mary Watson was born in Cornwall in 1860. She married Robert Watson in Cooktown, Queensland in 1879.", "context": "ctx:genes/mary-watson/obituary"}'

POST /extract

Extract knowledge with optional dry-run preview. Auth: none.

  • Request body: text (required), context (required), model? (default glm), mode?, dry_run? (boolean, default false).
  • Response: if dry_run=false: {model, context, facts_extracted, statements_ingested, anchors_attached, yield, elapsed_ms, document}. If dry_run=true: {model, facts_extracted, yield, dry_run: true, facts: [{subject (ex:kebab-case), predicate (camelCase), object (iri or {v, dt}), tier (1-8), confidence (0.0-1.0), notes}], elapsed_ms}. When dry_run=false, registers the source as donto_document + revision and writes donto_span + donto_evidence_link rows.
curl -X POST https://genes.apexpots.com/extract \
  -H 'Content-Type: application/json' \
  -d '{"text": "...", "context": "ctx:genes/test", "dry_run": true}'

POST /jobs/extract

Submit an extraction job (async, returns immediately). Auth: none.

  • Request body: text (required), context (required), model? (default glm), mode? (default opencode).
  • Response: {job_id: uuid}. Durable via Temporal (survives restarts); duplicate submissions for the same context are rejected. Poll GET /jobs/{job_id}.
curl -X POST https://genes.apexpots.com/jobs/extract \
  -H 'Content-Type: application/json' \
  -d '{"text": "...", "context": "ctx:genes/mary-watson/async"}' | jq '.job_id'

GET /jobs/{job_id}

Get job status and result. Auth: none.

  • Path param: job_id (uuid).
  • Response: {job_id, status (queued|extracting|ingesting|completed|failed), result?: {facts_extracted, statements_ingested, anchors_attached, model, context, elapsed_ms}, error?}.
curl https://genes.apexpots.com/jobs/{job_id} | jq '.status'

GET /jobs/{job_id}/facts

Get extracted facts from a completed job. Auth: none.

  • Path param: job_id (uuid). Query: limit? (default 200).
  • Response: array of {statement_id, subject, predicate, object_iri|object_lit, context, polarity, maturity, valid_lo, valid_hi, tx_lo}. Use statement_id with /claim/{id} for evidence.
curl 'https://genes.apexpots.com/jobs/{job_id}/facts?limit=50'

POST /assert

Assert a single statement into the graph. Auth: none.

  • Request body: subject (required, ex:kebab-case), predicate (required, camelCase), object_iri? (mutually exclusive with object_lit), object_lit? ({v, dt, lang?}, mutually exclusive with object_iri), context? (default donto:anonymous), polarity? (default asserted: asserted|negated|absent|unknown), maturity? (default 0; 0–4).
  • Response: {statement_id: uuid, existing: boolean}. Idempotent (content-hash dedup).
curl -X POST https://genes.apexpots.com/assert \
  -H 'Content-Type: application/json' \
  -d '{"subject": "ex:mary-watson", "predicate": "bornIn", "object_iri": "ex:cornwall-england", "context": "ctx:genes/mary-watson/manual", "maturity": 3}'

POST /assert/batch

Assert multiple statements atomically. Auth: none.

  • Request body: an array of /assert statement objects.
  • Response: {inserted, skipped, total}. Single transaction (all-or-none); per-statement dedup.
curl -X POST https://genes.apexpots.com/assert/batch \
  -H 'Content-Type: application/json' \
  -d '[{"subject": "ex:mary-watson", "predicate": "marriedTo", "object_iri": "ex:robert-watson"}, {"subject": "ex:mary-watson", "predicate": "bornOn", "object_lit": {"v": "1860-01-15", "dt": "xsd:date"}}]'

GET /search

Full-text search by name/label (trigram-indexed, ~5ms). Auth: none.

  • Query params: q (required) — trigram-matched, case-insensitive, partial; limit? (default 25; 1–100).
  • Response: {matches: [{subject (iri), label, count (fact count)}], q}. Ordered by fact count (most-connected first). The fastest way to find entities.
curl 'https://genes.apexpots.com/search?q=mary+watson&limit=10'

GET /history/{subject}

Get all facts about an entity (complete statement history). Auth: none.

  • Path param: subject (entity IRI). Query: limit?.
  • Response: {count, rows: [{statement_id, predicate, object_iri|object_lit, context, polarity, maturity, valid_lo, valid_hi, tx_lo, tx_hi}]}. Includes retracted statements (tx_hi set).
curl 'https://genes.apexpots.com/history/ex:mary-watson?limit=50'

GET /statement/{id}

Get a single statement by UUID. Auth: none.

  • Path param: id (uuid).
  • Response: {statement_id, subject, predicate, object_iri|object_lit, context, polarity, maturity, valid_lo, valid_hi, tx_lo, tx_hi}.
curl 'https://genes.apexpots.com/statement/{statement_uuid}'

POST /retract/{statement_id}

Retract a statement (bitemporal soft-delete). Auth: none.

  • Path param: statement_id (uuid).
  • Response: {retracted: boolean, statement_id}. Sets tx_hi; data not destroyed (as-of queries before retraction still return it). Idempotent.
curl -X POST 'https://genes.apexpots.com/retract/{statement_uuid}'

GET /subjects

List top subjects by fact count. Auth: none.

  • Request: none.
  • Response: {subjects: [{subject (iri), count}]} — top 50 by count descending.
curl 'https://genes.apexpots.com/subjects'

GET /contexts

List all contexts (scopes/namespaces). Auth: none.

  • Request: none.
  • Response: {contexts: [{context (iri), kind, mode, count}]}. Formats: ctx:genes/<topic>/<source>, ctx:extract/<file>/<model>, ctx:genealogy/research-db.
curl 'https://genes.apexpots.com/contexts'

GET /predicates

List all predicates with usage counts. Auth: none.

⚠️ Shadowed on the public host. At https://genes.apexpots.com/predicates this path is intercepted by the dontopedia web app and returns HTML, not API JSON (verified 2026-06-07). To get the JSON, call it on the internal port (http://localhost:7879/predicates via dontosrv) or use /viz/predicates, or query the substrate directly. Sibling paths (/subjects, /contexts, /search, /version) are not shadowed and correctly return JSON on genes.apexpots.com.

  • Request: none.
  • Response: {predicates: [{predicate, count}]} by count descending. (Top: rdf:type ~3.9M, donto:status ~1.6M, donto:textSpan ~1.2M, ex:knownAs ~1M, rdfs:label ~589K.)
curl 'http://localhost:7879/predicates'   # genes.apexpots.com/predicates returns the web app's HTML

POST /query

Run a DontoQL or SPARQL query. Auth: none.

  • Request body: {query: string} — DontoQL (MATCH ...) or SPARQL (SELECT/PREFIX ...). E.g. MATCH ?s ?p ?o LIMIT 20.
  • Response: array of result rows, each a JSON object of bound variables. Cost 1–10s depending on complexity.
curl -X POST 'https://genes.apexpots.com/query' \
  -H 'Content-Type: application/json' \
  -d '{"query": "MATCH ?s bornIn ?place LIMIT 10"}'

GET /connections/{entity}

Get all bidirectional connections (incoming + outgoing edges). Auth: none.

  • Path param: entity (iri). Query: limit? (default 500), context?, min_maturity? (default 0; 0–4).
  • Response: {outgoing: [{predicate, object_iri|object_lit, context, maturity}], incoming: [{predicate, subject, context, maturity}], entity}.
curl 'https://genes.apexpots.com/connections/ex:mary-watson?limit=100&min_maturity=2'

POST /graph/neighborhood

Get a neighborhood subgraph for visualization. Auth: none.

  • Request body: subject (required, center IRI), depth? (default 1, max 3), predicates? (string[]), context?, min_maturity? (default 0; 0–4), limit? (default 500, max 5000).
  • Response: {nodes: [{id (iri), label, type, degree}], edges: [{source, target, predicate, context, maturity}], center, depth} (D3-ready).
curl -X POST 'https://genes.apexpots.com/graph/neighborhood' \
  -H 'Content-Type: application/json' \
  -d '{"subject": "ex:mary-watson", "depth": 2, "predicates": ["marriedTo", "childOf", "parentOf"]}'

POST /graph/path

Find the shortest path between two entities. Auth: none.

  • Request body: source (required IRI), target (required IRI), max_depth? (default 6), predicates? (string[]).
  • Response: {paths: [[{subject, predicate, object}, ...]], found: boolean, depth}.
curl -X POST 'https://genes.apexpots.com/graph/path' \
  -H 'Content-Type: application/json' \
  -d '{"source": "ex:mary-watson", "target": "ex:cooktown-municipality"}'

POST /graph/subgraph

Get all edges matching given predicates (themed visualization). Auth: none.

  • Request body: predicates (required string[]), context?. E.g. family=["childOf","parentOf","marriedTo","siblingOf"].
  • Response: {nodes: [{id, label, type, degree}], edges: [{source, target, predicate, context, maturity}], node_count, edge_count} (D3-ready).
curl -X POST 'https://genes.apexpots.com/graph/subgraph' \
  -H 'Content-Type: application/json' \
  -d '{"predicates": ["bornIn", "diedIn", "marriedTo", "childOf"]}'

POST /align/register

Register a predicate alignment. Auth: none.

  • Request body: source (required predicate), target (required predicate), relation (required: exact_equivalent|inverse_equivalent|sub_property_of|close_match|not_equivalent), confidence? (default 1.0; 0.0–1.0).
  • Response: {alignment_id: uuid}. Must call POST /align/rebuild afterward for it to take effect.
curl -X POST 'https://genes.apexpots.com/align/register' \
  -H 'Content-Type: application/json' \
  -d '{"source": "bornInPlace", "target": "bornIn", "relation": "exact_equivalent", "confidence": 0.95}'

POST /align/rebuild

Rebuild the predicate closure index. Auth: none.

  • Request: none.
  • Response: {rows: integer} (closure table size, ~12K). Call after any /align/register. Cost 1–5s.
curl -X POST 'https://genes.apexpots.com/align/rebuild'

GET /align/suggest/{predicate}

Find similar predicates (trigram similarity). Auth: none.

  • Path param: predicate. Query: threshold? (default 0.3; 0.0–1.0), limit? (default 20).
  • Response: [{source, target, similarity, label}] by similarity descending.
curl 'https://genes.apexpots.com/align/suggest/bornInPlace?threshold=0.6&limit=10'

GET /evidence/{statement_id}

Get evidence spans linked to a statement. Auth: none.

  • Path param: statement_id (uuid).
  • Response: {evidence: [{link_type (produced_by|extracted_from|mentioned_in|supports|refutes|contextualizes), document (iri), span, surface_text, confidence (0-1)}]}. Empty if inserted via /assert.
curl 'https://genes.apexpots.com/evidence/{statement_uuid}'

GET /claim/{statement_id}

Full claim card (statement + evidence + arguments + obligations). Auth: none.

  • Path param: statement_id (uuid).
  • Response: {subject, predicate, object_iri|object_lit, context, polarity, maturity, asserted_at, evidence: [...], arguments: [{type (supports|refutes|undercuts|qualifies), statement}], obligations: [...], blockers: [...]}.
curl 'https://genes.apexpots.com/claim/{statement_uuid}'

GET /health

Health check (verifies API + dontosrv). Auth: none.

  • Response: {status (ok|degraded|down), dontosrv (ok|error), agent_instructions?}.
curl 'https://genes.apexpots.com/health'

GET /version

Version info. Auth: none.

  • Response: {dir, service (dontosrv), version, agent_instructions?}.
curl 'https://genes.apexpots.com/version'

GET /firehose/stream

Real-time SSE stream of database activity. Auth: none.

  • Request: none (Server-Sent Events).
  • Response: SSE stream of {audit_id, at, actor, action (insert|retract|...), statement_id, detail}. Backed by Postgres LISTEN/NOTIFY on donto_firehose / donto_audit.
curl 'https://genes.apexpots.com/firehose/stream' 2>/dev/null | head -20

GET /firehose/recent

Recent audit events (last N). Auth: none.

  • Query: limit? (default 100).
  • Response: [{audit_id, at, actor, action, statement_id, detail}].
curl 'https://genes.apexpots.com/firehose/recent?limit=20'

GET /jobs

List all extraction jobs with status. Auth: none.

  • Query: status? (queued|extracting|ingesting|completed|failed).
  • Response: {jobs: [{job_id, status, text_preview, context, model, created_at, updated_at, result?}]}.
curl 'https://genes.apexpots.com/jobs?status=completed'

dontosrv (core substrate)

The core bitemporal claim substrate HTTP sidecar — ~70 endpoints covering the claim write-path, rich read/search, evidence linking, the Trust Kernel, obligations/reactions, predicate alignment, shape validation & rule derivation, and schema discovery. It is reachable internally at localhost:7879; the public genealogy surface (https://genes.apexpots.com) proxies a curated subset. All endpoints return JSON unless noted. Reads are open; writes are not gated at the HTTP layer (policy gates apply at the SQL layer per context/action). Examples below use the internal http://localhost:7879 base; substitute your deployment's reachable host.

Claims are bitemporal (tx_time/valid_time) with polarity (asserted/negated/absent/unknown) and maturity (E0–E4). When DONTO_ENFORCE_CLAIM_INVARIANT=true, anchorless/unflagged claims land at maturity E1 (never rejected, just un-promoted).

Health & metrics

GET /health

Service health check. Auth: none. Returns text/plain: ok.

curl http://localhost:7879/health

GET /version

Service version. Auth: none. Returns {service, version, dir}.

curl http://localhost:7879/version

GET /metrics

Prometheus-format substrate health metrics. Auth: none. Returns text/plain gauges (donto_up, donto_currently_believed_statements, donto_distinct_subjects, donto_distinct_predicates, donto_distinct_contexts, donto_evidence_links, donto_contested_subject_predicate_pairs, donto_total_contradiction_pressure, donto_consumer_namespaces, donto_registered_overlays, donto_identity_hypotheses, donto_argument_edges, donto_anchor_coverage_pct, donto_statements_estimated, …). Same numeric fields as the donto_v_substrate_health view.

curl http://localhost:7879/metrics | grep donto_currently_believed_statements

Claim write-path

POST /assert

Assert one bitemporal claim. Auth: none.

  • Request body: {subject, predicate, object_iri?, object_lit?: {v, dt, lang?}, context?='donto:anonymous', polarity?='asserted', maturity?=0, valid_from?: date, valid_to?: date, anchor?: {revision_id: uuid, start: int32, end: int32, surface_text: string, link_type?='extracted_from'}, hypothesis_only?=false}.
  • Response: {statement_id: uuid} or {error}. When anchor is provided, materializes donto_span + donto_evidence_link.
curl -X POST http://localhost:7879/assert -H 'Content-Type: application/json' \
  -d '{"subject":"ex:person1","predicate":"ex:motherOf","object_iri":"ex:person2","context":"donto:evidence","maturity":2}'

POST /assert/batch

Assert many claims in one call. Auth: none.

  • Request body: {statements: [StatementInput]} (each like /assert).
  • Response: {inserted: int64, anchors_attached: int64} or {error}.
curl -X POST http://localhost:7879/assert/batch -H 'Content-Type: application/json' \
  -d '{"statements":[{"subject":"ex:a","predicate":"ex:p","object_iri":"ex:b"},{"subject":"ex:c","predicate":"ex:q","object_iri":"ex:d"}]}'

POST /retract

Retract a claim (close its tx_time interval). Auth: none.

  • Request body: {statement_id: uuid}.
  • Response: {statement_id, retracted: boolean}. Retracted rows stay in donto_statement with closed intervals.
curl -X POST http://localhost:7879/retract -H 'Content-Type: application/json' \
  -d '{"statement_id":"49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae"}'

POST /contexts/ensure

Idempotently create/ensure a context. Auth: none.

  • Request body: {iri, kind?='source', mode?='permissive', parent?}. kindsource|hypothesis|derived|snapshot|user|…; modepermissive|strict|….
  • Response: {iri, ok: boolean} or {error}.
curl -X POST http://localhost:7879/contexts/ensure -H 'Content-Type: application/json' \
  -d '{"iri":"donto:genes-evidence","kind":"source","mode":"permissive"}'

Read & search

GET /search

Label/IRI/literal substring search; distinct subjects ranked by live-row count. Auth: none.

  • Query: q (required), limit? (default 25, max 100).
  • Response: {q, matches: [{subject, label|null, count}]}.
curl 'http://localhost:7879/search?q=person&limit=10'

GET /history/{subject}

All statements about a subject (open + closed, all contexts/polarities). Auth: none.

  • Path: subject (IRI). Query: limit? (default 2000, max 20000), context?, predicate?, from? (date), to? (date), include_retracted? (default true).
  • Response: {subject, total, limit, statements: [{statement_id, subject, predicate, object_iri, object_lit, context, polarity, maturity, valid_lo, valid_hi, tx_lo, tx_hi, lineage: [uuid]}]}.
curl 'http://localhost:7879/history/ex:person1?limit=100&context=donto:evidence'

GET /statement/{id}

Full statement detail: row, lineage, audit log, certificate, siblings. Auth: none.

  • Path: id (uuid).
  • Response: {statement: StatementRow, lineage: {sources: [...], derived: [...]}, audit: [...], certificate: {}|null, siblings: [...]}.
curl 'http://localhost:7879/statement/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae'

GET /claim/{id}

Claim card (donto_claim_card): the claim + evidence + contradiction context. Auth: none.

  • Path: id (uuid).
  • Response: free-form JSON projected by the donto_claim_card(uuid) SQL function (shape varies by claim topology).
curl 'http://localhost:7879/claim/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae'

POST /recall

Policy-gated recall projection (donto_recall_projection): identity-resolved, bitemporally-sliced rows with per-row effective actions. Auth: none.

  • Request body: {holder, action ('read_metadata'|'read_content'|'quote'|'derive_claims'|…), subject?, predicate?, object_iri?, scope?: {root, depth?}, polarity?='asserted', min_maturity?=0, as_of_tx?: datetime, as_of_valid?: date, lens_name?, limit?=200, permitted_only?=false}.
  • Response: {holder, action, lens|null, as_of|null, row_count, rows: [StatementRow + {resolved_subject|null, resolved_object|null, effective_actions: {}, action_allowed: boolean}]}.
curl -X POST http://localhost:7879/recall -H 'Content-Type: application/json' \
  -d '{"holder":"[email protected]","action":"read_content","limit":50}'

GET /subjects

List subjects with live statement count (limit 5000). Auth: none. Returns {subjects: [{subject, count}]} (live counts, descending).

curl 'http://localhost:7879/subjects'

GET /subjects/all

List all subjects including retracted-only ones. Auth: none. Returns {subjects: [{subject, count}]}.

curl 'http://localhost:7879/subjects/all'

POST /subjects/refresh

Refresh the subject-count materialized view (donto_subject_count). Auth: none. Returns {ok: boolean}.

curl -X POST http://localhost:7879/subjects/refresh

GET /inbound/{subject}

Statements where the subject is the object (inverse view). Auth: none. Returns {subject, inbound: [StatementRow]}.

curl 'http://localhost:7879/inbound/ex:person1'

GET /cluster/{subject}

Subject's identity cluster (identity-resolved neighbours). Auth: none. Returns {subject, cluster: [{resolved_id, confidence}]}.

curl 'http://localhost:7879/cluster/ex:person1'

GET /context-facts

Distinct (subject, predicate, object) triple count per context. Auth: none. Returns {contexts: [{context, fact_count}]}.

curl 'http://localhost:7879/context-facts'

GET /context-spans

Context-scoped evidence span counts. Auth: none. Returns {contexts: [{context, span_count}]}.

curl 'http://localhost:7879/context-spans'

Identity & predicates

POST /subjects/merge

Merge two subjects (identity binding). Auth: none. Body {source, target}{source, target, ok}.

curl -X POST http://localhost:7879/subjects/merge -H 'Content-Type: application/json' \
  -d '{"source":"ex:person-a","target":"ex:person-b"}'

GET /predicates/align-suggest

Suggest predicate alignments (cross-vocabulary candidates). Auth: none. Query: limit? (default 100). Returns {suggestions: [{source, target, confidence}]}.

curl 'http://localhost:7879/predicates/align-suggest?limit=50'

POST /predicates/merge

Merge two predicates (canonical registration). Auth: none. Body {source, target}{source, target, ok}.

curl -X POST http://localhost:7879/predicates/merge -H 'Content-Type: application/json' \
  -d '{"source":"ex:parentOf","target":"ex:parent_of"}'

GET /predicates/fragmentation

Predicate fragmentation report (variant clustering). Auth: none. Returns {predicates: [{canonical, variant_count, variants: [string]}]}.

curl 'http://localhost:7879/predicates/fragmentation'

GET /predicates/suggest

Suggest predicates by substring/similarity. Auth: none. Query: q, limit? (default 25). Returns {q, predicates: [{iri, label|null, count}]}.

curl 'http://localhost:7879/predicates/suggest?q=mother&limit=10'

GET /entities/suggest

Suggest entities (subjects) by substring/similarity. Auth: none. Query: q, limit? (default 25). Returns {q, entities: [{subject, label|null, count}]}.

curl 'http://localhost:7879/entities/suggest?q=john&limit=10'

POST /predicates/refresh

Refresh the predicate-count view (donto_predicate_count). Auth: none. Returns {ok}.

curl -X POST http://localhost:7879/predicates/refresh

GET /predicates

List predicates by usage descending (limit 500). Auth: none. Returns {predicates: [{predicate, count}]}.

curl 'http://localhost:7879/predicates'

GET /contexts

List contexts (limit 500). Auth: none. Returns {contexts: [{context, kind, mode, count}]}.

curl 'http://localhost:7879/contexts'

POST /contexts/lookup

Targeted metadata lookup for known context IRIs (faster than /contexts on hot paths). Auth: none. Body {iris: [string]}{contexts: [{context, kind, mode, count}]}.

curl -X POST http://localhost:7879/contexts/lookup -H 'Content-Type: application/json' \
  -d '{"iris":["donto:evidence","donto:hypothesis"]}'

Documents & evidence

POST /sources/lookup

Metadata for registered document sources. Auth: none. Body {iris: [string]}{sources: [{document_id, document_iri, label|null, source_url|null, source_kind|null, media_type, revision_id|null, has_body}]}.

curl -X POST http://localhost:7879/sources/lookup -H 'Content-Type: application/json' \
  -d '{"iris":["donto:genes-paper-1"]}'

POST /statements/evidence

Evidence (spans + links) for statements. Auth: none. Body {statement_ids: [uuid]}{evidence: {<statement_id>: [{link_type, target_document_id|null, target_span_id|null, confidence|null}]}}.

curl -X POST http://localhost:7879/statements/evidence -H 'Content-Type: application/json' \
  -d '{"statement_ids":["49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae"]}'

POST /documents/register

Register a document source (legacy; no required policy). Auth: none. Body {iri, media_type?='text/plain', label?, source_url?, language?, policy_iri?}{document_id, iri, policy_iri|null, warning?}. Deprecated in favour of /sources/register.

curl -X POST http://localhost:7879/documents/register -H 'Content-Type: application/json' \
  -d '{"iri":"donto:genes-paper-2026","media_type":"text/html"}'

POST /sources/register

Register a document source with a required policy. Auth: none. Body {iri, source_kind, policy_iri, media_type?, label?, source_url?, language?}{document_id, iri, policy_iri}. No source without policy.

curl -X POST http://localhost:7879/sources/register -H 'Content-Type: application/json' \
  -d '{"iri":"donto:genes-paper-2026","source_kind":"academic-paper","policy_iri":"donto:policy-public-read"}'

POST /documents/revision

Add a revision (version) of a document body. Auth: none. Body {document_id, body?, parser_version?}{revision_id}.

curl -X POST http://localhost:7879/documents/revision -H 'Content-Type: application/json' \
  -d '{"document_id":"...","body":"The full document text here..."}'

GET /documents/revision/{revision_id}/body

Retrieve the full body text of a revision. Auth: none. Returns {revision_id, body, byte_size} or 404 if the revision has no text body.

curl 'http://localhost:7879/documents/revision/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae/body'

POST /evidence/link/span

Link a statement to an evidence span. Auth: none. Body {statement_id, span_id, link_type?='extracted_from', confidence?, context?}{link_id}.

curl -X POST http://localhost:7879/evidence/link/span -H 'Content-Type: application/json' \
  -d '{"statement_id":"49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae","span_id":"..."}'

GET /evidence/{stmt}

All evidence for a statement (donto_evidence_for). Auth: none. Path: stmt (uuid). Returns {evidence: [{link_id, link_type, target_document_id|null, target_revision_id|null, target_span_id|null, target_annotation_id|null, target_run_id|null, target_statement_id|null, confidence|null}]}.

curl 'http://localhost:7879/evidence/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae'

Arguments & reactions

POST /arguments/assert

Assert a typed argument edge between two statements. Auth: none. Body {source: uuid, target: uuid, relation ('supports'|'rebuts'|'undercuts'|'endorses'|'supersedes'|'qualifies'|'potentially_same'|'same_referent'|'same_event'), context?='donto:anonymous', strength?, agent_id?, evidence?}{argument_id}.

curl -X POST http://localhost:7879/arguments/assert -H 'Content-Type: application/json' \
  -d '{"source":"...","target":"...","relation":"supports","strength":0.8}'

GET /arguments/{stmt}

All argument edges touching a statement (donto_arguments_for). Auth: none. Returns {arguments: [{argument_id, source, target, relation, strength|null, context}]}.

curl 'http://localhost:7879/arguments/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae'

GET /arguments/frontier

Contradiction frontier: statements ranked by net attack/support pressure. Auth: none. Returns {frontier: [{statement_id, attack_count, support_count, net_pressure}]} (net_pressure = support_count - attack_count).

curl 'http://localhost:7879/arguments/frontier'

POST /react

Attach a reaction to a statement (endorse/reject/cite/supersede). Auth: none. Body {source: uuid, kind ('endorses'|'rejects'|'cites'|'supersedes'), object_iri?, context?='donto:anonymous', actor?}{reaction_id}.

curl -X POST http://localhost:7879/react -H 'Content-Type: application/json' \
  -d '{"source":"49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae","kind":"endorses"}'

GET /reactions/{id}

Enumerate current reactions to a statement. Auth: none. Returns {reactions: [{reaction_id, kind, object_iri|null, context, polarity}], counts: {endorses, rejects, cites, supersedes}}.

curl 'http://localhost:7879/reactions/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae'

Trust kernel (policy, authorisation, attestations, agents)

POST /policy/assign

Assign a policy to a target. Auth: none. Body {target_kind ('context'|'document'|'overlay'|…), target_id, policy_iri, assigned_by?='system'}{assignment_id}.

curl -X POST http://localhost:7879/policy/assign -H 'Content-Type: application/json' \
  -d '{"target_kind":"context","target_id":"donto:evidence","policy_iri":"donto:policy-public-read"}'

GET /policy/effective/{target_kind}/{target_id}

Effective actions the caller may take on a target. Auth: none (caller from headers if present). Returns {target_kind, target_id, caller, actions: {<action>: boolean}}.

curl 'http://localhost:7879/policy/effective/context/donto:evidence'

POST /authorise

Authorisation probe (check without executing). Auth: none. Body {target_kind, target_id, action}{caller, target_kind, target_id, action, allowed: boolean}.

curl -X POST http://localhost:7879/authorise -H 'Content-Type: application/json' \
  -d '{"target_kind":"context","target_id":"donto:evidence","action":"read_content"}'

POST /protected/read

Policy-gated read: execute a SQL select only if the caller's action is allowed. Auth: none. Body {target_kind, target_id, action, query}{rows: [...]} or {error, reason}. (Input not sanitised — SQL-injection risk; operator-internal.)

curl -X POST http://localhost:7879/protected/read -H 'Content-Type: application/json' \
  -d '{"target_kind":"context","target_id":"donto:evidence","action":"read_content","query":"SELECT * FROM ..."}'

POST /attestations

Issue an attestation (Trust Kernel): holder may perform actions under a policy within time/scope. Auth: none. Body {holder, issuer, policy_iri, actions: [string], purpose, rationale, expires_at?}{attestation_id}.

curl -X POST http://localhost:7879/attestations -H 'Content-Type: application/json' \
  -d '{"holder":"alice@genes","issuer":"admin","policy_iri":"donto:policy-genes-read","actions":["read_content"],"purpose":"research","rationale":"approved"}'

POST /attestations/{id}/revoke

Revoke an attestation. Auth: none. Path: id (uuid). Body {revoked_by?='system', reason?}{revoked: boolean, attestation_id}.

curl -X POST http://localhost:7879/attestations/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae/revoke \
  -H 'Content-Type: application/json' -d '{"revoked_by":"admin","reason":"policy change"}'

POST /agents/register

Register an agent (LLM, person, bot, system). Auth: none. Body {iri, agent_type?='custom', label?, model_id?}{agent_id, iri}.

curl -X POST http://localhost:7879/agents/register -H 'Content-Type: application/json' \
  -d '{"iri":"donto:claude-opus","agent_type":"llm","model_id":"claude-opus-4"}'

POST /agents/bind

Bind an agent to a context (ownership/stewardship). Auth: none. Body {agent_id, context, role?='owner'}{ok}.

curl -X POST http://localhost:7879/agents/bind -H 'Content-Type: application/json' \
  -d '{"agent_id":"...","context":"donto:genes-evidence","role":"owner"}'

Obligations

POST /obligations/emit

Emit an obligation (task, reminder, review request). Auth: none. Body {statement_id, obligation_type, context?='donto:anonymous', priority?=0, detail?, assigned_agent?}{obligation_id}.

curl -X POST http://localhost:7879/obligations/emit -H 'Content-Type: application/json' \
  -d '{"statement_id":"...","obligation_type":"review-needed","priority":1}'

POST /obligations/resolve

Resolve an obligation. Auth: none. Body {obligation_id, resolved_by?, status?='resolved'}{resolved: boolean}.

curl -X POST http://localhost:7879/obligations/resolve -H 'Content-Type: application/json' \
  -d '{"obligation_id":"..."}'

POST /obligations/open

List open obligations (filterable). Auth: none. Body {obligation_type?, context?, limit?=100}{obligations: [{obligation_id, statement_id|null, obligation_type, priority, context, assigned_agent|null}]}.

curl -X POST http://localhost:7879/obligations/open -H 'Content-Type: application/json' \
  -d '{"obligation_type":"review-needed","limit":50}'

GET /obligations/summary

Obligation summary (counts by type + status). Auth: none. Returns {summary: [{obligation_type, status, count}]}.

curl 'http://localhost:7879/obligations/summary'

Shapes, rules, certificates

POST /shapes/validate

Validate claims against a shape (constraint) schema. Auth: none. Body {shape_iri, scope: {} (ContextScope)}{shape_iri, focus_count, violations: [{focus, reason, evidence: [uuid]}], source ('builtin'|'cached'|'lean')}. Built-in shapes: builtin:functional/<pred>, builtin:datatype/<pred>/<dt>, builtin:range/*.

curl -X POST http://localhost:7879/shapes/validate -H 'Content-Type: application/json' \
  -d '{"shape_iri":"builtin:functional/ex:motherOf","scope":{"root":"donto:evidence"}}'

POST /rules/derive

Run a derivation rule and emit derived statements. Auth: none. Body {rule_iri, scope: {} (ContextScope), into: string}{rule_iri, into, emitted, source}. Built-in rules: builtin:transitive/<pred>, builtin:inverse/<pred>/<inv>, builtin:symmetric/<pred>. Idempotent.

curl -X POST http://localhost:7879/rules/derive -H 'Content-Type: application/json' \
  -d '{"rule_iri":"builtin:transitive/ex:ancestorOf","scope":{"root":"donto:evidence"},"into":"donto:derived"}'

POST /certificates/attach

Attach a certificate to a statement (proof of derivation/justification). Auth: none. Body {statement_id, kind ('direct_assertion'|'substitution'|'transitive_closure'|'confidence_justification'|'shape_entailment'|'hypothesis_scoped'|'replay'), rule_iri?, inputs?: [uuid], body: {}, signature?}{statement_id, kind}.

curl -X POST http://localhost:7879/certificates/attach -H 'Content-Type: application/json' \
  -d '{"statement_id":"...","kind":"transitive_closure","body":{"predicate":"ex:ancestorOf"}}'

POST /certificates/verify/{stmt}

Re-run certificate verification for a statement. Auth: none. Path: stmt (uuid). Returns {statement_id, kind, ok: boolean, reason?}.

curl -X POST http://localhost:7879/certificates/verify/49bbf329-e9d2-4c83-a56f-5c6c0aaf99ae

Alignment & descriptors

POST /alignment/register

Register a predicate alignment. Auth: none. Body {source, target, relation ('exact_equivalent'|'inverse_equivalent'|'sub_property_of'|'close_match'|'decomposition'|'not_equivalent'), confidence, valid_lo?, valid_hi?, run_id?, provenance?, actor?}{alignment_id}.

curl -X POST http://localhost:7879/alignment/register -H 'Content-Type: application/json' \
  -d '{"source":"ex:parentOf","target":"rdfs:parent_of","relation":"exact_equivalent","confidence":0.95}'

POST /alignment/retract

Retract a predicate alignment. Auth: none. Body {alignment_id}{alignment_id, retracted: boolean}.

curl -X POST http://localhost:7879/alignment/retract -H 'Content-Type: application/json' \
  -d '{"alignment_id":"..."}'

POST /alignment/rebuild-closure

Rebuild the predicate closure (donto_rebuild_predicate_closure()). Auth: none. Returns {rows: int64}.

curl -X POST http://localhost:7879/alignment/rebuild-closure

POST /alignment/runs/start

Start an alignment run (batch matching/disambiguation). Auth: none. Body {run_type, model_id?, config?, source_predicates?}{run_id}.

curl -X POST http://localhost:7879/alignment/runs/start -H 'Content-Type: application/json' \
  -d '{"run_type":"embedding-similarity","model_id":"sentence-transformers/all-MiniLM-L6-v2"}'

POST /alignment/runs/complete

Complete an alignment run. Auth: none. Body {run_id, status, proposed?, accepted?, rejected?}{run_id, ok: boolean}.

curl -X POST http://localhost:7879/alignment/runs/complete -H 'Content-Type: application/json' \
  -d '{"run_id":"...","status":"success","proposed":150,"accepted":140,"rejected":10}'

POST /descriptors/upsert

Register/update a predicate descriptor. Auth: none. Body {predicate_iri, gloss?, subject_type?, object_type?, domain?, example_subject?, example_object?}{predicate_iri, ok: boolean}.

curl -X POST http://localhost:7879/descriptors/upsert -H 'Content-Type: application/json' \
  -d '{"predicate_iri":"ex:motherOf","gloss":"X is the mother of Y","subject_type":"Person","object_type":"Person"}'

POST /descriptors/nearest

Find nearest predicates to a descriptor (similarity search). Auth: none. Body {gloss, limit?=10}{candidates: [{predicate, gloss, similarity}]}.

curl -X POST http://localhost:7879/descriptors/nearest -H 'Content-Type: application/json' \
  -d '{"gloss":"X is parent of Y","limit":5}'

Overlays (shadows)

POST /shadow/materialize

Materialize an overlay shadow (consumer-owned projection). Auth: none. Body {overlay_iri, scope?, include_history?}{overlay_iri, rows_materialized}.

curl -X POST http://localhost:7879/shadow/materialize -H 'Content-Type: application/json' \
  -d '{"overlay_iri":"donto:genes-overlay","include_history":false}'

POST /shadow/rebuild

Rebuild all overlay shadows. Auth: none. Returns {overlays_rebuilt: int64}.

curl -X POST http://localhost:7879/shadow/rebuild

Query languages

POST /sparql

SPARQL query execution. Auth: none. Body {query, scope_preset?}{rows: [...]} or {error}. Subset: PREFIX, SELECT, WHERE, GRAPH, FILTER, LIMIT, OFFSET.

curl -X POST http://localhost:7879/sparql -H 'Content-Type: application/json' \
  -d '{"query":"SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10"}'

POST /dontoql

DontoQL query execution (donto's native query language; richer than SPARQL — bitemporal scoping, policy gating, identity lensing, rules). Auth: none. Body {query, scope_preset?}{rows: [...]} or {error}.

curl -X POST http://localhost:7879/dontoql -H 'Content-Type: application/json' \
  -d '{"query":"MATCH ?s ex:parentOf ?o FILTER ?s != ?o"}'

POST /dir

Directory service: resolve IRIs to structured metadata. Auth: none. Body {iris: [string]}{<iri>: {label|null, description|null, ...}}.

curl -X POST http://localhost:7879/dir -H 'Content-Type: application/json' \
  -d '{"iris":["ex:parentOf","ex:person1"]}'

Schema discovery (/discovery/*)

All GET, auth: none. These describe the substrate's schema, vocabulary, policies, and contract — ideal for SDK generators and curation tooling.

GET /discovery/contract-version

Substrate API contract version + supported clauses + action list. Returns {contract_version, dontosrv_version, dir_version, supported_dontoql_clauses: [string], supported_sparql_subset: [string], actions: [string]}. Supported actions: read_metadata, read_content, quote, derive_claims, derive_embeddings, translate, summarize, export_*, train_model, publish_release, share_with_third_party, federated_query, request_deletion.

curl 'http://localhost:7879/discovery/contract-version'

GET /discovery/contexts

List contexts (cap 5000). Returns {contexts: [{iri, kind, parent|null, label|null, mode, closed_at|null}], limit}.

curl 'http://localhost:7879/discovery/contexts'

GET /discovery/contexts/{iri}

Context detail with assigned policies and effective actions. Returns {iri, kind, parent|null, label|null, mode, closed_at|null, policies: [string], effective_actions: {}}.

curl 'http://localhost:7879/discovery/contexts/donto:evidence'

GET /discovery/predicates

List predicates by usage descending (cap 5000). Returns {predicates: [{iri, label|null, status, minting_status, statement_count}], limit}.

curl 'http://localhost:7879/discovery/predicates'

GET /discovery/predicates/{iri}

Predicate detail with descriptor + alignments (top 50). Returns {iri, label|null, description|null, status, minting_status, canonical_of|null, inverse_of|null, characteristics: {symmetric, transitive, functional, inverse_functional}, descriptor: {gloss|null, subject_type|null, object_type|null, domain|null, example_subject|null, example_object|null}, statement_count, alignments: [{source_iri, target_iri, relation, confidence}]}.

curl 'http://localhost:7879/discovery/predicates/ex:parentOf'

GET /discovery/predicate-inventory

Ranked predicate listing under an optional ?prefix=<curie>, capped by ?limit (default 100, clamped 1–5000). Returns {predicates: [{predicate, namespace, n}], prefix|null, limit}.

curl 'http://localhost:7879/discovery/predicate-inventory?prefix=ex&limit=50'

GET /discovery/predicate-namespaces

Namespace-level rollup: distinct predicates + total statements per CURIE prefix. Returns {namespaces: [{namespace, distinct_predicates, total_statements}]}.

curl 'http://localhost:7879/discovery/predicate-namespaces'

GET /discovery/modalities

Allowed modality values. Returns {modalities: [{name, description}]}.

curl 'http://localhost:7879/discovery/modalities'

GET /discovery/extraction-levels

Allowed extraction-level values (claim provenance classification). Returns {extraction_levels: [string], observed_in_corpus: [string]}. Standard list: quoted, table_read, example_observed, source_generalization, cross_source_inference, model_hypothesis, human_hypothesis, manual_entry, registry_import, adapter_import.

curl 'http://localhost:7879/discovery/extraction-levels'

GET /discovery/policies

Policy capsules (cap 1000). Returns {policies: [{policy_iri, policy_kind, revocation_status, expiry|null, summary|null, allowed_actions: {}, inheritance_rule}]}.

curl 'http://localhost:7879/discovery/policies'

GET /discovery/policies/{iri}

Policy capsule detail + assignment count. Returns {policy_iri, policy_kind, authority_refs: [string], allowed_actions: {}, inheritance_rule, revocation_status, expiry|null, summary|null, labels: {}, created_at, created_by, assignments_count}.

curl 'http://localhost:7879/discovery/policies/donto:policy-public-read'

GET /discovery/frame-types

Registered frame types (structural schemas). Returns {frame_types: [{kind, label|null, description|null}]}.

curl 'http://localhost:7879/discovery/frame-types'

GET /discovery/alignment-relations

The alignment-edge relation types. Returns {alignment_relations: [{name, description}]}. Relations: exact_equivalent, inverse_equivalent, sub_property_of, close_match, decomposition, not_equivalent.

curl 'http://localhost:7879/discovery/alignment-relations'

GET /discovery/identity-hypotheses

Registered identity hypotheses + clustered symbol counts. Returns {identity_hypotheses: [{hypothesis_id, name, description|null, threshold_same, threshold_distinct, method, authority|null, status, clustered_symbols}]}.

curl 'http://localhost:7879/discovery/identity-hypotheses'

GET /discovery/overlays

Registered consumer overlays. Returns {overlays: [{overlay_iri, consumer_iri, table_name, owns_key, policy_inherits, fixed_policy_iri|null, bitemporal, description|null, registered_by, registered_at}]}.

curl 'http://localhost:7879/discovery/overlays'

GET /discovery/dontoql-grammar

DontoQL grammar BNF (text/plain). Covers SCOPE, PRESET, MATCH, FILTER, POLARITY, MATURITY, IDENTITY, IDENTITY_LENS, PREDICATES, MODALITY, EXTRACTION_LEVEL, TRANSACTION_TIME, POLICY ALLOWS, SCHEMA_LENS, EXPANDS_FROM, ORDER BY, WITH evidence, PROJECT, LIMIT, OFFSET.

curl 'http://localhost:7879/discovery/dontoql-grammar' | head -30

GET /discovery/substrate-health

Substrate-level health dashboard (donto_v_substrate_health view; same numeric fields as GET /metrics). Returns {currently_believed_statements, retracted_statements, distinct_subjects, distinct_predicates, distinct_contexts, evidence_links, contested_subject_predicate_pairs, total_contradiction_pressure, consumer_namespaces, registered_overlays, identity_hypotheses, argument_edges, anchor_coverage_pct, statements_estimated}.

curl 'http://localhost:7879/discovery/substrate-health'

GET /discovery/openapi

The hand-written OpenAPI 3.1 specification (contract for SDK generators). Returns the JSON spec.

curl 'http://localhost:7879/discovery/openapi' | jq '.info'

donto-admin

https://admin.donto.org (internal localhost:3103, Next.js 16) — a read-only observability console over the substrate. Every route returns the uniform envelope {ok: boolean, data: T, at: ISO8601} (errors: {ok: false, error, at}, HTTP 500), never cached. Access is via the donto_ro Postgres role (SELECT-only, default_transaction_read_only=on); there are no per-request credentials. limit params are clamped to [1, max]; offset defaults to 0 (≥0). Cheap endpoints use reltuples estimates (no statement-table scans). No OpenAPI spec.

GET /api/health

Liveness probe for all observability surfaces (DB + HTTP services).

  • Response: {ok, data: {db: {ok, error?, target (redacted DSN), serverVersion?, currentUser?, readOnly?}, services: [{name, url, ok, detail|null}], temporalUiUrl}, at}. Probes donto-api (:8000), dontosrv (:7879), donto-memory (:7900), and the read-only Postgres connection; degrades gracefully if any service is down.
curl -s https://admin.donto.org/api/health | jq

GET /api/overview

At-a-glance health dashboard: substrate metrics + statement counts + daemon + extraction jobs.

  • Response: {ok, data: {health: SubstrateHealth|null, daemon: DaemonStatus, statementEstimate: number|null, jobsRunning, jobsCompleted, jobsFailed, jobsTotal}, at}. SubstrateHealth carries the same fields as donto_v_substrate_health. Each section degrades to null/0 on a fresh donto.
curl -s https://admin.donto.org/api/overview | jq .data.health

GET /api/contexts

Top contexts by statement count (from the donto_mv_context_stats matview) + totals.

  • Query: limit? (1–500, default 50).
  • Response: {ok, data: {rows: [{iri, kind|null, mode|null, label|null, statementCount, openCount|null, refreshedAt|null}], totalContexts|null, totalStatements|null}, at}.
curl -s 'https://admin.donto.org/api/contexts?limit=10' | jq .data.rows

GET /api/fabric

Substrate fabric metrics: embedding coverage, closure breakdown, identity machinery, evidence, top predicates, GLM cap state.

  • Response: {ok, data: {coverage: {predicateEmbeddings, predicateTotal, entityEmbeddings, evidenceLinks, believedStatements, anchorPct}, closure: {total|null, byRelation: [{relation, n}]}, identity: {edges, clusters, proposals, hypotheses, memberships}, glmCap: {capUntil|null, capReason|null, glmCallsThisWindow, glmWindowStarted|null}|null, topPredicates: [{predicate, namespace, n}]}, at}.
curl -s https://admin.donto.org/api/fabric | jq .data.topPredicates

GET /api/processes

Live instance activity: extraction jobs, memory jobs, alignment/extraction runs, daemon heartbeat, service health, Temporal UI link.

  • Response: {ok, data: {jobs: {reachable, jobs: [ExtractionJob], running, completed, failed}, memoryJobs: {reachable, jobs: [MemoryJob], ok, failed, memorize, recall}, extractionRuns: [...], alignmentRuns: [...], daemon: DaemonStatus, daemonTicks: [DaemonTick], services: [ServiceHealth], temporalUiUrl}, at}. Fetches extraction jobs from donto-api /jobs, memory jobs from donto-memory /jobs/list.json, daemon status from DB introspection; each source degrades gracefully.
curl -s https://admin.donto.org/api/processes | jq '.data | {jobs: .jobs, services: .services}'

GET /api/jobs

Paginated list of ingested documents (generic ingestion-job explorer).

  • Query: q? (search iri/label), adapter? (parser_version filter), limit? (1–200, default 50), offset? (default 0).
  • Response: {ok, data: {jobs: [{id (document_id), iri, label|null, mediaType|null, sourceKind|null, adapter|null, registeredBy|null, parserVersion|null, status|null, byteSize|null, createdAt|null, nStatements}], total, limit, offset, adapters: [{adapter, n}]}, at}.
curl -s 'https://admin.donto.org/api/jobs?limit=10&offset=0' | jq .data.jobs

GET /api/jobs/[id]

One ingested document: source + extracted statements + LLM transcript.

  • Path: id (document_id UUID).
  • Response: {ok, data: {id, iri, label, mediaType, sourceKind, adapter, registeredBy, parserVersion, status, byteSize, createdAt, nStatements, source: string|null, sourceChars, context, statements: [{subject, predicate, object, isIri, maturity (0-7), confidence|null}], transcript: string|null, transcriptFile: string|null}, at}. 404 if not found.
curl -s 'https://admin.donto.org/api/jobs/550e8400-e29b-41d4-a716-446655440000' | jq .data

GET /api/jobs/[id]/charts

Complete chart datasets for one job: totals, maturity/polarity/evidence distributions, predicates, reach, entity shape.

  • Path: id (document_id UUID).
  • Response: {ok, data: {totals: {statements, anchored, hypothesis, unanchored, aligned, argued, distinctPredicates, distinctSubjects, withValidTime}, maturity: [Pair] (0-7), polarity: [Pair], datatype: [Pair], evidence: [Pair], validTime: [Pair], objectKind: [Pair], predicateFreq: [{predicate, count}], hapax, contested: {subject, predicate, objects: [{object, weight, e, n}]}|null, entityShape: [{subject, predicates}], predicateReach: [{predicate, inJob, substrate, folds}], entityReach: [{entity, inJob, everywhere}]}, at} where Pair = {label, count}. Bounded to 5000 claims.
curl -s 'https://admin.donto.org/api/jobs/550e8400-e29b-41d4-a716-446655440000/charts' | jq '.data.totals'

GET /api/jobs/[id]/nebula

Belief Nebula data + layout for one job: claims with embeddings, contention systems, argument filaments, gas density.

  • Path: id (document_id UUID).
  • Response: {ok, data: {claims: [{id, subject, predicate, object, isIri, maturityE (0-7), hasEvidence, evidenceCount, confidence, polarity (flags & 3), contextDomain, validFrom, validTo, validUnbounded, txFrom, txRetracted, predFamily, hasEntityEmbedding, hasPredicateEmbedding, placedByEmbedding, x (PCA), y}], systems: [{subject, predicate, objects: [{object, weight, evidenceLevel, polarity}], barycenter: [x, y]}], filaments: [{source, target, relation, strength}], gas: [{domain, density}]}, at}. Bounded ≤5000 claims; server-side PCA of entity⊕predicate embeddings (deterministic layout).
curl -s 'https://admin.donto.org/api/jobs/550e8400-e29b-41d4-a716-446655440000/nebula' | jq '.data.claims | length'

GET /api/jobs/[id]/panel/[key]

One lazy-loaded donto-capability lens for a job.

  • Path: id (document_id UUID), key — one of evidence-anchors, contradictions, predicate-alignment, embedding-neighbors, bitemporal, identity, cross-source-lens, provenance-maturity, entity-reach, outgoing-graph.
  • Response: {ok, data: PanelData[], at} (shape varies by key; e.g. evidence-anchors: [{subject, predicate, object, anchor (surface_text), start_offset, end_offset, confidence, e_level}]; entity-reach: [{subject, in_job, believed_everywhere, beyond_this_job, pct_from_this_job}]). 404 if key unknown; each panel bounded to 50–200 rows.
curl -s 'https://admin.donto.org/api/jobs/550e8400-e29b-41d4-a716-446655440000/panel/evidence-anchors' | jq '.data | length'

GET /api/relations

Introspected list of every public relation (tables, matviews, views, partitioned parents) with sizes and statistics.

  • Response: {ok, data: {count, relations: [{name, kind (r/m/v/p), kindLabel, estRows, totalBytes, tableBytes, indexBytes, totalSize, tableSize, indexSize, lastAnalyze|null, lastVacuum|null, comment|null}]}, at}. Pure pg_catalog query (no hardcoded table list), sorted by total relation size DESC.
curl -s https://admin.donto.org/api/relations | jq '.data.relations | length'

GET /api/table/[rel]

Introspected table/matview/view details: columns, indexes, recent rows, count estimate, value distributions.

  • Path: rel (relation name, validated against the live introspected allow-set). Query: view? (all|columns|rows|count|distributions, default all), limit? (1–200, default 50).
  • Response: {ok, data: {relation: Relation, columns?: [{name, type, notNull, hasDefault, isPk, position, comment|null}], indexes?: [{name, def, isPrimary, isUnique}], count?, recent?: {rows: [...], totalOffset?}, distributions?: [{column, value, count}]}, at}. Injection-guarded (rel validated, %I identifier quoting). Big relations (>50k rows) return a reltuples estimate instead of an exact count.
curl -s 'https://admin.donto.org/api/table/donto_statement?view=columns' | jq '.data.columns'

GET /api/beam

BEAM-10M extraction-firehose progress.

  • Response: {ok, data: {beam: {available: boolean, total (chunks enqueued), pending, leased (in-flight), done, failed, claims (total claims emitted)}}, at}. Reads entirely from donto_extract_queue; degrades gracefully (available: false) on a donto without an extraction run.
curl -s https://admin.donto.org/api/beam | jq .data.beam

MCP access

The donto-memory MCP server exposes the three core memory operations as Model Context Protocol tools, so MCP-aware agents (Claude Code, Codex, etc.) can read and write the substrate without hand-rolling HTTP calls. It is a thin front-end over donto-memory's /recall, /search, and /memorize endpoints; it talks to https://memories.apexpots.com by default (override with DONTO_MEMORY_URL). No auth is required (the substrate is public by design). There are two interchangeable implementations exposing the identical 3 tools: a Node ESM npm package (primary) and a stdlib-only Python single-file server.

Tools

  1. donto_recall(holder, query, limit=10) — recall a holder's own memories. Hybrid lexical + semantic, holder-scoped, bitemporal (prefers the latest valid_from for multi-valued attributes). Wraps POST /recall.
  2. donto_search(query, context_prefix=null, limit=10) — substrate-wide full-text search across all contexts (genealogy, memory, research-db, …). Returns ranked results. Wraps POST /search.
  3. donto_memorize(holder, text, valid_from=null, extract=true) — store a memory as an episodic chunk, optionally extracting semantic claims via LLM. Fast modes return immediately; slow modes are queued. Wraps POST /memorize.

Install

NPM (hosted), runs the package directly:

npx -y donto-memory-mcp

Register with an MCP client:

codex mcp add donto-memory -- npx -y donto-memory-mcp
claude mcp add donto-memory -- npx -y donto-memory-mcp

Config block with a custom endpoint:

{
  "mcpServers": {
    "donto-memory": {
      "command": "npx",
      "args": ["-y", "donto-memory-mcp"],
      "env": { "DONTO_MEMORY_URL": "http://127.0.0.1:7900" }
    }
  }
}

Python (local, stdlib-only, no dependencies):

python3 /mnt/donto-data/workspace/donto-memory/mcp/donto_mcp.py

MCP docs are published at https://mcp.donto.org (index + /agents.md + /llms.txt + /mcp.json).


Quickstart recipes

1. Search the substrate, then read a claim

Find entities/claims by name, then pull the full evidence-backed claim card for one of them.

# Substrate-wide name search (via donto-memory)
curl -X POST https://memories.apexpots.com/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"caroline brown","context_prefix":"ctx:genes","limit":10}'

# Take a statement_id from the rows, then fetch its claim card (via donto-api / dontosrv)
curl 'https://genes.apexpots.com/claim/<statement_uuid>'

2. Memorize, then recall (agentic memory)

Store an episodic memory with a real-world valid date, then recall it holder-scoped.

# Memorize (sync for fast modes; episodic always stored, semantic claims extracted if configured)
curl -X POST https://memories.apexpots.com/memorize \
  -H 'Content-Type: application/json' \
  -d '{"holder":"agent:my-bot","session_id":"discord:user:12345","text":"I met Annie Davis at Cooktown Festival in 1979.","valid_from":"1979-01-01"}'

# Recall what this holder knows (hybrid lexical + semantic)
curl -X POST https://memories.apexpots.com/recall \
  -H 'Content-Type: application/json' \
  -d '{"holder":"agent:my-bot","query":"who did I meet","limit":10}'

3. Run an embedding worker (lease → embed → submit)

A spare machine pulls work, computes bge-small (384-dim) vectors, and submits them. Remember the bearer token and the curl/8 UA.

# 1. (Operator) top up the queue from the source table
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"target":"memory_chunk","limit":10000}' \
  https://donto.org/embed/enqueue

# 2. Lease a batch to embed
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"worker_id":"worker-1","n":100,"target":"memory_chunk"}' \
  https://donto.org/embed/lease
# -> {"batch":[{"target":"memory_chunk","item_id":"abc-123","text":"...","model":"BAAI/bge-small-en-v1.5","dim":384}, ...]}

# 3. Compute vectors locally (bge-small, 384 dims), then submit
curl -A 'curl/8' -X POST -H 'Authorization: Bearer <TOKEN>' -H 'Content-Type: application/json' \
  -d '{"items":[{"target":"memory_chunk","item_id":"abc-123","vector":[0.1,0.2]}]}' \
  https://donto.org/embed/submit
# -> {"upserted":1}

# Monitor progress
curl -A 'curl/8' -H 'Authorization: Bearer <TOKEN>' https://donto.org/embed/stats

4. Extract and ingest a document (then verify in the graph)

Preview facts with a dry run, commit them, and confirm they landed in the substrate.

# Preview (no writes)
curl -X POST https://genes.apexpots.com/extract \
  -H 'Content-Type: application/json' \
  -d '{"text":"Mary Watson was born in Cornwall in 1860. She married Robert Watson in Cooktown, Queensland in 1879.","context":"ctx:genes/mary-watson/obituary","dry_run":true}'

# Commit (extract + ingest, anchors attached to the source)
curl -X POST https://genes.apexpots.com/extract-and-ingest \
  -H 'Content-Type: application/json' \
  -d '{"text":"Mary Watson was born in Cornwall in 1860. She married Robert Watson in Cooktown, Queensland in 1879.","context":"ctx:genes/mary-watson/obituary"}'

# Verify: find the entity, then read its full history
curl 'https://genes.apexpots.com/search?q=mary+watson&limit=5'
curl 'https://genes.apexpots.com/history/ex:mary-watson?limit=50'

For batch/durable extraction, submit a job instead and poll:

JOB=$(curl -sX POST https://genes.apexpots.com/jobs/extract \
  -H 'Content-Type: application/json' \
  -d '{"text":"...","context":"ctx:genes/mary-watson/async"}' | jq -r '.job_id')
curl "https://genes.apexpots.com/jobs/$JOB" | jq '.status'
curl "https://genes.apexpots.com/jobs/$JOB/facts?limit=50"

Verified 2026-06-07. Live-probed endpoints: donto-memory — GET /health, GET /version, GET /substrate, GET /api, GET /modules, POST /recall, POST /search, POST /search/resources, GET /reconsolidate/queue, GET /explore/stats.json, GET /openapi.json. donto-memory-mcp — POST /recall, POST /search, POST /memorize. donto-embed-coordinator — GET /embed/health (shape + Caddy routing). donto-api — GET /search, GET /subjects, GET /contexts, GET /health, GET /version (OpenAPI fetched live; note GET /predicates is shadowed by the web app on the public host and returns HTML). dontosrv — GET /health, GET /version, GET /metrics, POST /assert, GET /discovery/contract-version. donto-admin — GET /api/health, GET /api/overview. All other endpoints documented from source code.

donto API reference · donto