API reference
REST routes & MCP tools
Every route below is copied from crates/verity-server/src/main.rs; every tool from crates/verity-mcp/src/main.rs. Default listen address is http://127.0.0.1:7717. The MCP server is a thin stdio proxy over these same REST routes — no enforcement logic lives in it.
Auth & conventions
There are two authorization surfaces:
- Scope-handle plane — the agent-facing verbs (
recall,get,episodes,actions,activity,briefs,forget,files,knowledgepropose). Each takes ascope_handleminted byPOST /v1/scopes; the tenant and permissions come from the signed handle, never the body. - Admin/ingest plane — the trusted connector and admin verbs (
/v1/admin/*,/v1/ingest/*,/v1/webhooksmint,/v1/manifests, knowledge publish). WhenVERITY_ADMIN_TOKENis set, these requireAuthorization: Bearer <token>(constant-time check). Unset = dev mode, warned once at startup.
The inbound webhook lane (POST /wh/{token}) is authorized by the capability token in the URL path itself.
Scopes
- POST /v1/scopes
- Mint an HMAC scope handle. Body:
tenant_id, and eitherprincipals(int tokens, dev/no-ReBAC) orsubject("user:...", ReBAC-resolved — mutually exclusive with principals); optionalentity_scope,max_confidentiality(defaultinternal),actor_sub,actor_azp,ttl_seconds(default 3600),purpose(clamps the ceiling; unknown → 422). Returns{ scope_handle, expires_at }.
http
POST /v1/scopes
{ "tenant_id":"c1e6...", "principals":[7,11], "entity_scope":["account:acme"],
"max_confidentiality":"confidential", "actor_azp":"agent:sales-bot" }
// 200 → { "scope_handle":"vs_9f2...", "expires_at":"2026-07-10T18:00:00Z" }Recall & get
- POST /v1/recall
- Scoped hybrid search. Body:
scope_handle,text(embedded locally for the dense leg) and/orembedding,k(default 8, capped 100). Returns an array of hits with content, entity tags, trust tier, timestamps, and ACL provenance. Restricted-class hits pass a live recheck before returning. - GET /v1/records/{source}/{entity}/{field}
- Point read of one structured L1 field. Query:
scope_handle, optionalas_of(RFC 3339 — bi-temporal read; absent = current). 404 if there is no value for that key/time.
http
POST /v1/recall
{ "scope_handle":"vs_9f2...", "text":"renewal risks and last pricing discussion", "k":8 }
// 200 → [ { "chunk_id":"...", "content":"...", "entity_tags":["account:acme"],
// "confidentiality":"internal", "acl_provenance":"mirrored", ... }, ... ]
GET /v1/records/postgresql:crm.opportunities/opp-1/amount?scope_handle=vs_9f2...&as_of=2026-07-10T11:59:00Z
// 200 → { "value":50000, "valid_from":"...", "valid_to":"...", ... } (superseded value, labeled)Episodes / actions / activity / briefs
- POST /v1/episodes
- Append an observation (immutable episode + searchable chunk). Body:
scope_handle,observation, optionalentities(subset of the scope). Returns{ episode_id }. - POST /v1/actions
- Record an action on the activity timeline (idempotent on
action_id). Body:scope_handle,action_id,action_type(dotted, e.g.quote.issued), optionalentities,summary, optionalpayload,outcome(succeeded|failed|pending),occurred_at. Actor identity is stamped from the scope, not the body. - GET /v1/activity
- Scoped activity timeline for an entity. Query:
scope_handle,entity, optionalsince,action_types(comma-separated exact orprefix.*),limit(default 50). - GET /v1/briefs/{entity}
- One-call entity state:
recent_memory+recent_activity(always re-derived under the caller's scope) +is_stale/last_synced_at/source_version+ a derived-scope-gatedsummary(null unless the caller intersects every contributing source). - POST /v1/forget
- Invalidate a memory. Body:
scope_handle,ref={kind: "chunk"|"episode", id},reason. An episode cascade retires its derived chunks/facts and triggers the knowledge retraction recount. Tenant comes from the handle. - GET /v1/subscribe
- Server-sent-events change stream (capped; 429 beyond). The MCP layer uses a pull tool instead of forwarding this over stdio.
- GET /v1/slo/freshness
- Freshness SLO percentiles from ingest samples (event-time → queryable).
http
POST /v1/actions
{ "scope_handle":"vs_9f2...", "action_id":"q1", "action_type":"quote.issued",
"summary":"Issued renewal quote at $84,000 (12mo, net-30).",
"outcome":"succeeded", "occurred_at":"2026-07-10T17:41:02Z" }
// 200 → { "recorded": true }Ingest (admin plane)
- POST /v1/ingest/debezium
- Debezium change envelope(s) → deterministic keyed L1 upserts. Query:
tenant_id, optionalpk(defaultid). Body: one envelope or an array. Returns{ facts_inserted, facts_superseded, facts_unchanged, facts_retired }. Provenance mirrored. - POST /v1/ingest/documents
- One document version → one episode + paragraph chunks. Body:
tenant_id,source,document_id,content, optionalentities,visibility(int tokens),acl_provenance(mirrored|approximated|admin-assigned—quarantinedis rejected), optionalvalid_from. The contract the Google Drive connector codes against.
Webhooks & files
- POST /v1/webhooks admin
- Mint a scoped ingest URL. Body:
tenant_id,name,visibility(non-empty int tokens), optionalentity_scope,confidentiality,manifest_id. Returns{ webhook_id, url }; the raw token is shown once. - DELETE /v1/webhooks/{id} admin
- Revoke — the URL stops resolving. Returns
{ revoked }. - POST /wh/{token}
- Inbound lane. Native shape:
content/observation(→ chunk),facts[],entities[], optionalvisibility[](may narrow, never widen → 403). Unknown shapes → 202{quarantined:true}. Manifest-bound URLs route through the manifest runtime. - POST /v1/files
- Multipart drop: fields
scope_handle, optionalentities(comma-separated),file. Splits to paragraph chunks + embeds. Returns{ episode_id, chunks_indexed }. - GET /v1/media/{id}
- Fetch stored media by id (scope-checked).
- POST /v1/media/{id}/sign
- Mint an HMAC-signed, scope-checked media URL.
Knowledge
- POST /v1/knowledge
- Propose a cross-customer generalization (a proposal, never a publish). Body:
scope_handle,statement(about categories, never entities),categories,evidence(episode ids). Runs the de-identification gate; failures are stored quarantined, auditable. - GET /v1/knowledge admin
- Review queue. Query:
tenant_id, optionalstatus. Returns items with admin-exactdistinct_entities, bucketedsupport_tier, merge reason, and evidence lineage. - POST /v1/knowledge/{id}/publish admin
- Publish. Body:
tenant_id,visibility(broad tokens),k_min(clamped to ≥ 3). Grants broad visibility; embeds the statement for dense recall. - GET /v1/admin/knowledge/{id} admin
- Full review detail incl. the de-identification gate result and complete evidence list.
- POST /v1/admin/knowledge/{id}/reject admin
- Reject a candidate; the same canonical statement will not resurrect. Rejecting a published item is refused — retraction is
forget's job.
Manifests
- POST /v1/manifests admin
- Upload a manifest as a schema-validated draft. Body:
{tenant_id, yaml}. Re-uploading the samesource.namedemotes it to draft. - GET /v1/manifests admin
- List with status/tier/acl summary. Query:
tenant_id. - POST /v1/manifests/{id}/activate admin
- The human gate. Body:
{tenant_id, approved_by}. Refuses (422) whenacl_policyis absent or violates the tier contract; approval is recorded in the audit log.
Admin
- POST /v1/admin/tenants
- Create a tenant. Body:
{name}→{tenant_id}. - POST /v1/admin/principals
- Upsert principal strings → int tokens (idempotent). Body:
{tenant_id, principals[]}. - POST / DELETE /v1/admin/groups
- Write/remove a membership tuple (requires ReBAC). DELETE writes revocation tombstones first (fail-closed ordering).
- GET /v1/admin/audit
- Audit log of scoped reads. Access-controlled and itself audited.
- GET /v1/admin/quarantine
- Recent quarantined webhook payloads, newest first. Query:
tenant_id,limit. - POST /v1/admin/erasure
- Compliance erasure — crypto-shred + lineage hard-purge. Admin/compliance verb, never reachable from a scope handle.
- GET /v1/admin/dsar/export
- DSAR export of everything attributable to a subject.
- POST /v1/admin/briefs/refresh
- Recompute stale briefs for a tenant (the sleep-time path). Query:
tenant. - POST /v1/admin/reembed/batch, /v1/admin/reembed/cutover
- Embedding-migration: dual-vector backfill, then coverage-gated route cutover (refuses V2 below 100% coverage unless
force). - POST /v1/admin/connector-status GET too
- Connector status/staleness for the scope inspector.
- Consolidation & tags
/v1/admin/consolidation/lease,/complete,/merge-candidates;/v1/admin/tag-suggestions(+/{id}/approve) — the L2/knowledge worker plane and probabilistic-tag approval.- GET /v1/admin/media
- List stored media for a tenant.
- GET /healthz, GET /ui
- Health check; the read-only scope-inspector UI (handle decode + live probes + quarantine/audit/freshness panels).
MCP tools (14)
Connect any MCP-capable agent with verity-cli mcp install (prints the exact claude mcp add verity ...). Identity (tenant, principals, actor sub/azp) is process configuration and is never a tool argument. Workflow: memory_open_scope once, pass the handle to everything else.
| Tool | Arguments | When to use |
|---|---|---|
memory_open_scope | entity_scope?, purpose?, ttl_seconds? | FIRST, once per task — get a scope_handle. |
memory_recall | scope_handle, text?, embedding?, k? | Open-ended questions ("what do we know about X?"). |
memory_get | scope_handle, source, entity, field | Exact current value of one structured field. |
memory_brief | scope_handle, entity | Start of work on an entity — current state in one call. |
memory_remember | scope_handle, observation, entities? | Persist durable knowledge learned during the task. |
memory_record_action | scope_handle, action_id, action_type, entities?, summary, payload?, outcome, occurred_at | AFTER a side-effecting action, so other agents don't repeat it. |
memory_activity | scope_handle, entity, since?, action_types?, limit? | BEFORE acting — what other agents did on this entity. |
memory_poll_changes | scope_handle, entities, since | Between turns — pull changes since a cursor (pass back next_since verbatim). |
memory_ingest_text | scope_handle, content, entities?, document_id? | Ingest a document-sized piece of text held in-context. |
memory_ingest_file | scope_handle, path, entities? | Ingest a local UTF-8 text file (≤ 512 KB). |
memory_ingest_url | scope_handle, url, entities? | Fetch + remember a public http(s) page. |
memory_propose_learning | scope_handle, statement, categories?, evidence? | Propose a cross-customer generalization (never names a customer). |
memory_forget | scope_handle, ref_kind (chunk|episode), id, reason | Retract a wrong/sensitive memory, audited. |
memory_whoami | — | Diagnostic: the server's configured identity defaults. |
note
memory_ingest_text currently proxies POST /v1/episodes (a document_id arg is reserved but not yet sent); memory_ingest_file/_url proxy the multipart POST /v1/files. memory_poll_changes combines GET /v1/activity and GET /v1/briefs/{entity} client-side — MCP delivers no pushes into a running turn, so this pull tool is the honest delivery model.