API reference
The REST surface at mcp.hunta.ai. Bearer JWT auth; the tenant is bound to the token.
Base URL: https://mcp.hunta.ai. Authenticate with Authorization: Bearer <key>. The tenant is the
token's tid claim; no endpoint takes a tenant argument, so a key can never read or write outside its
own store. Scopes on the key gate each operation (see Scopes). A key without the required
scope gets 403 {"error":"insufficient_scope","required":[...]}; a missing or invalid key gets 401.
Hunta's interface is built around three verbs: Gather (write memory), Recall (read it back),
and Instinct (the governed reflex set). Over MCP those are the tool names your agent calls. Over
REST they map to the endpoints below, which use a common /v1/memories lexicon so any client already
built for a memory API works unchanged.
Two write paths exist, and the scope on your key decides which one you are on:
- Owner keys (
memory:write) write canon directly withPOST /v1/memories. - Agent keys (
memory:capture) can only propose:POST /v1/memories/capturestages a candidate in the review queue and returns acandidate_id. An agent key callingPOST /v1/memoriesgets403 insufficient_scope; it does not fall through to staging.
Gather
The write motion. Over MCP, the gather tool writes canon and capture proposes. Over REST, the
/v1/memories endpoints.
Write a memory (canon)
POST /v1/memoriesScope memory:write. Writes straight to canon through the full pipeline (extraction, dedup,
provenance). Returns 201 {"id": "<memory-id>", "tenant": "<tid>"}.
{
"text": "Our design partner is Acme Corp.",
"agent": "default",
"valid_from": "2026-03-01T00:00:00Z",
"supersedes": null
}| Field | Required | Meaning |
|---|---|---|
text | yes | The memory. 400 if empty. |
agent | no | Display label for the writer (default default). The authoritative writer identity is always the token's subject. |
valid_from | no | ISO-8601 event-time: when the fact became true in the world, as distinct from when you recorded it. Must not be in the future. A date the extractor infers from the text takes precedence. |
supersedes | no | The id of the current fact this text replaces (new in 0.58.0). That fact is closed bi-temporally and text is put in force as its successor, verbatim; the extractor is not run. Use it whenever you record that something previously remembered is no longer so. A fresh memory written beside the old fact would leave the stale one in force. 404 if the fact does not exist, 409 if it is not current. |
On the free tier, a write past the monthly allowance returns
402 {"error":"quota_exceeded","detail":"...","upgrade":"https://console.hunta.cloud/crosstalk/plans"}.
Paid plans are metered instead and never return 402.
Propose a memory (staging)
POST /v1/memories/captureScope memory:capture (or memory:write). Stages text as a candidate. Candidates are never
returned by recall or search until a curator promotes them. Returns
201 {"candidate_id": "<id>", "tenant": "<tid>", "status": "pending"}.
{ "text": "Acme renewed for another year.", "agent": "support-bot", "source": "api" }source (default api) tags where the capture came from; the reconciler's auto lane only admits
candidates whose source is on the tenant's allow-list (see set_auto_sources).
List memories
GET /v1/memoriesScope memory:read. Returns {"memories": [...], "count": n}, every raw memory in your store.
Get one memory
GET /v1/memories/{id}Scope memory:read. 404 {"error":"not_found"} if the id is not in your tenant.
Forget a memory (cascade erasure)
DELETE /v1/memories/{id}Scope memory:write. GDPR erasure: permanently deletes the raw memory, every fact derived from it
(current and superseded), and any entity thereby orphaned. Returns the delete counts. To preview the
blast radius first, call GET /v1/facts?source_memory_id={id}.
Recall
The read motion: retrieve sealed facts by relevance, with point-in-time support. Over MCP, the recall
and search tools. Over REST, /v1/memories/search.
POST /v1/memories/searchScope memory:read. Returns {"results": [...]}; each hit carries memory, id, agent, score
and, for facts, kind, source_memory_id and valid_from.
{ "query": "design partner", "limit": 5, "agent": null, "as_of": "2026-06-01T00:00:00Z", "entity": "Acme" }| Field | Default | Meaning |
|---|---|---|
query | "" | Semantic plus lexical query. Empty means filter-only listing. |
limit | 10 | Maximum hits. |
agent | none | Only facts written under this agent label. |
as_of | none | ISO-8601 bi-temporal point-in-time read: facts in force at that instant. |
entity | none | Only facts about a named entity. |
Free-tier reads past the monthly allowance return the same 402 quota_exceeded response as writes.
Facts
Facts are the distilled, current-truth statements recall reads from. Each carries reconciliation
metadata: slot, valid_from / valid_to, superseded_by, retraction_reason.
List facts
GET /v1/factsScope memory:read. Returns {"facts": [...], "count": n}.
| Query parameter | Default | Meaning |
|---|---|---|
source_memory_id | none | Only facts derived from this raw memory (the DELETE /v1/memories/{id} cascade preview). |
superseded_by | none | Only facts closed by this successor fact id. |
include_superseded | false | 1 or true to include closed and retracted facts. |
limit | 50 | Maximum rows. 400 if not an integer. |
Get one fact
GET /v1/facts/{id}Scope memory:read. Adds created_at (when it was learned), corroboration_count (distinct attested
writers) and decrypted entity names. 404 if absent.
Supersede a fact
POST /v1/facts/{id}/supersedeScope memory:curate (or memory:write). Surgically corrects one fact: the target is closed
bi-temporally (valid_to plus superseded_by, never deleted, so as_of history stays intact) and
statement is put in force as its successor. The extractor is not run. If the correction already
exists as a current fact the target is closed pointing at it. Who closed it is stamped from your
token. Returns 201 with new_fact_id; 404 if the fact is absent, 409 if it is not current.
{ "statement": "Our design partner is Beta Ltd.", "agent": "default", "valid_from": "2026-05-01T00:00:00Z" }valid_from (optional, not future) is when the corrected statement became true; else now.
Invalidate a fact
POST /v1/facts/{id}/invalidateScope memory:curate (or memory:write). Retracts one fact without a successor because it was simply
wrong. superseded_by stays empty; the optional reason is stored encrypted for audit.
{ "reason": "Mis-extracted from a draft, never true." }Forget one fact
DELETE /v1/facts/{id}Scope memory:write only (deliberately not memory:curate: correction is curation, deletion is not).
Erases one fact; its siblings and the raw source memory survive.
Candidates (the review queue)
Everything an agent key proposes lands here. Owners and curators review it; recall never sees it.
List candidates
GET /v1/candidatesScope any of memory:read, memory:capture, memory:curate, memory:write (reading the queue is a
read). Returns {"candidates": [...], "count": n}.
| Query parameter | Default | Meaning |
|---|---|---|
status | pending | pending, promoted, rejected or expired. |
limit | 50 | Maximum rows. |
kind | all | fact (promotable proposals) or breadcrumb (high-volume auto-captures from hooks). |
Promote a candidate
POST /v1/candidates/{id}/promoteScope memory:curate (or memory:write). Writes the candidate into canon through the full curated
pipeline. Body is optional:
{ "text": "Distilled statement to store instead of the raw capture", "agent": "default", "severity": 3 }The original capture stays on the candidate row for audit. curated_by and the lane (human or
auto) are stamped from your token, never from parameters. Returns 201; 404 if the candidate is
absent, 409 if it is no longer pending.
Reject a candidate
POST /v1/candidates/{id}/rejectScope memory:curate (or memory:write). Closes the candidate as rejected, stamped with who
rejected it, so a wrong rejection is auditable too. 200, or 404 if not pending.
Consolidate a candidate
POST /v1/candidates/{id}/consolidateScope memory:curate (or memory:write). Admits a merge proposal (a consolidate-kind candidate) as
one curator-composed Instinct entry and retires the entries it replaces.
{ "action": "cloudron.exec", "symptom": "...", "guidance": "...", "retire_slots": ["..."], "severity": 4 }action, symptom and guidance are required (400 otherwise); retire_slots and severity are
optional. Returns 201; 404 if absent, 409 if not pending.
Run a reconcile pass
POST /v1/reconcileScope memory:curate (or memory:write). Runs one two-lane triage pass over your pending candidates:
breadcrumbs are skipped, anything instruction-like, credential-adjacent or third-party is annotated
for human review, and clean facts go to the auto lane (promoted only when your tenant's reconcile
mode is on). Returns the run report.
{ "limit": 50, "retriage": false }limit is clamped to 1..200. retriage: true re-evaluates already-triaged candidates (use after
changing the mode or auto sources).
Instinct
Check the reflex set
GET /v1/errataScope memory:read. The governed reflex set (standing constraints). Not metered.
- With
?action=<name>it is the deterministic per-action gate query an agent or hook runs before a risky action:{"action": ..., "errata": [...], "count": n}. Optionallimit(default 25, max 100). - Without
actionit returns the full current set grouped by action:{"actions": [{"action": ..., "errata": [...], "count": n}], "count": total}. Optionallimit(default 200, max 500) andmin_severity.
Propose an Instinct entry
POST /v1/errata/candidatesScope memory:capture (or memory:write). The contract a failure hook posts to: stage a candidate
bound to action with the verbatim symptom that fooled the agent. Guidance is added by a curator at
promotion; nothing is surfaced by the gate query until then. A recurring failure collapses onto the one
pending candidate. Returns 201.
{ "action": "cloudron.exec", "symptom": "scheduler: NOT INSTALLED", "retrieval_keys": { "tool": ["cloudron"] } }retrieval_keys is optional: concrete tokens from the real invocation (tool, verbs, host, exit
signature), up to 16 keys of up to 16 short strings each. Wildcards are dropped.
Writing a governed entry directly (with guidance) is a curated write: over MCP, capture_errata
(scope memory:curate).
Export
GET /v1/exportScope memory:curate (or the legacy full-access memory key). Not available to agent keys, and not
to a bare memory:write key either: a whole-tenant dump is a curator action. Returns every fact with
its full bi-temporal history plus an Ed25519 signature over facts[] that you can re-verify offline
against the published JWKS. Capped at 50,000 facts; truncated: true flags a store beyond that. Exit
is never gated by plan.
Account
Who am I
GET /v1/whoamiAny valid key. Returns {"tenant", "subject", "scopes"} as resolved from the token.
Store metrics
GET /v1/metricsScope memory:read. Counts, facts versus raw memories, unprocessed extraction backlog,
decrypt-failure counter, extractor egress and the last attestation timestamp.
Attestation
Run the isolation proof
POST /v1/admin/verify-isolation?nonce=<your-nonce>Scope memory:read (your own tenant's proof is a read). Runs the live probes and returns the signed
receipt. The nonce is echoed into the signed manifest for freshness.
Attestation report
GET /v1/admin/attestation/report?nonce=<your-nonce>Scope memory:read. A summary (probe results, timestamps, key id) plus the signed receipt, as shown
in the console's Provenance page.
Published keys
GET /.well-known/jwks.jsonNo auth. The Ed25519 public keys: hunta-auth-1 (token verification) and hunta-attest-1
(attestation signing). Pin hunta-attest-1 to verify receipts offline.
Liveness
GET /healthzNo auth. {"status": "ok", "backend", "version", ...}. An OpenAPI 3.1 description of this surface is
at GET /v1/openapi.json, also unauthenticated.
MCP
The same engine is an MCP server at https://mcp.hunta.ai/mcp (Streamable HTTP). Every tool checks
the same scopes as its REST counterpart. See Connect over MCP for the full tool
list with parameters.
Scopes
| Scope | Grants |
|---|---|
memory:read | recall, search, list memories and facts, candidates queue (read), Instinct gate query, metrics, attestation |
memory:capture | propose candidates into staging (/v1/memories/capture, /v1/errata/candidates), read the queue |
memory:write | direct writes to canon, forget (cascade and per-fact), plus everything capture allows and every curate action except export |
memory:curate | promote, reject and consolidate candidates, supersede and invalidate facts, reconcile, export |
Legacy scope memory means full access. Two further scopes exist but are not customer-mintable:
memory:admin (per-tenant reconciler settings: POST /v1/admin/reconcile-mode,
POST /v1/admin/auto-sources, POST /v1/admin/retry-unprocessed) and memory:provision (the
portal's key-minting and Tribe orchestration routes under /v1/admin/token, /v1/teams). A request to
mint either is refused.
Mint least-privilege keys from the console's Keys page: agent keys get memory:capture plus
memory:read; memory:write and memory:curate stay with owners.