Quick Start
VITNA produces Ed25519-signed evidence records that anyone can verify offline with a published public key and an open-source verifier, with no need to trust VITNA's servers. It is a cooperative guardrail with heuristic detection, and those limits are documented publicly. Its purpose is not prevention. It is independently verifiable proof that an AI agent's actions were checked and allowed.
In category terms: VITNA issues verifiable compliance receipts for agent actions. Each checked action produces a decision record, and those records are packaged into an Ed25519-signed agent action receipt that a third party can verify offline. See Verify Evidence for the two verification tiers, and What VITNA Sends for exactly what leaves your machine.
Get your agent sending events to VITNA in under 2 minutes. No SDK required, just one HTTP call.
REST API
The VITNA API is a simple REST API. Every endpoint accepts JSON and returns JSON.
POST /api/ingest
Log a single event from your agent. Core endpoint, call this every time your agent does something.
Headers
Response
🔒 Storage Mode
Every event request accepts a storage_mode field. When set to "local", VITNA validates your API key and enforces rate limits, but never writes to our database. Events are returned to the client and stored in your browser's IndexedDB. Nothing persists on COSTRINITY servers.
POST /api/ingest (batch)
Log multiple events in a single call. Up to 100 events per request.
GET /api/graph
Returns the activity graph for an agent, nodes are actions (or individual events), edges are causal links inferred from parent_id / trace_id / run_id / temporal proximity. Used by the dashboard’s Activity Graph.
| agent_id* | UUID | Required. |
| owner_id* | UUID | Required. Must own the agent. |
| group | "action"|"event" | Default 'action'. 'event' returns one node per event (raw timeline). |
| limit | int 1–5000 | Default 2000. Caps the number of events scanned. |
| include_noise | 0|1 | Default 0. When 0, baseline events (heartbeat, ping, health-check, *-heartbeat, idle_*) are filtered server-side BEFORE the temporal-proximity edge calculation. The dashboard passes 1 to keep the count badge live; CLI consumers usually want the default. |
| include_system | 0|1 | Default 0. When 1, VITNA-internal cron events (heartbeat_missed, budget_warning, key_rotated, …) are included. |
TypeScript SDK
@costrinity/vitna-compliance-mcp (the MCP server, see the MCP section). Until the SDK ships, use the REST API directly, which needs no dependency at all.Planned: an optional typed wrapper around the REST API adding retry logic, batching, and type safety.
ElizaOS Plugin
Zero-config plugin for ElizaOS agents. Automatically logs every action your agent takes.
@costrinity/vitna-compliance-mcp in the meantime.vitna.log() calls manually.MCP Proxy
For agents that use the Model Context Protocol (MCP). The proxy sidecars any MCP server, captures every tool call, resource read, prompt fetch, and notification as a VITNA event. Zero changes to your agent code.
Add it to your MCP client config (Claude Desktop, Claude Code, Cursor, etc.):
mcp.tools.call, mcp.resources.read, etc. with request params, response result, and duration on the same event. The proxy is fire-and-forget: VITNA outages never block the MCP flow.Event Schema
Full schema of all fields accepted by the ingest endpoint.
Alerts
Smart alerts watch your agents for you. Configure them from the dashboard, no code needed.
Trigger when an agent hasn't sent an event in N minutes. Catches stuck, crashed, or looping agents.
Alert when error events exceed X% of total events in a rolling window.
Alert when cost_sol total exceeds your limit in 1 hour. Protects against runaway spending.
Alert when a specific action appears, e.g. fire immediately if action === 'emergency-stop'.
Delivery channels
- ✓ Email (all plans)
- ✓ Webhook POST (Pro+)
- ✓ Slack webhook (Enterprise)
Webhooks
VITNA can POST to your endpoint when an alert fires. Available on Pro and above.
Webhook requests include a X-VITNA-Signature header (HMAC-SHA256) so you can verify authenticity. Configure your endpoint URL in the Dashboard → Alerts → Settings.
Rate Limits
Rate limits are per API key. Exceeded requests return 429.
events[] array count as 1 request regardless of event count. Use batching to maximize throughput on high-frequency agents.Verify VITNA evidence yourself
One minute, no account, no trust in VITNA's servers required. Download the open-source verifier and a real signed sample bundle, then check the signature offline with Node 18 or newer:
curl -sO https://raw.githubusercontent.com/COSTRINITY/vitna-compliance-mcp/main/verify-evidence.mjs
curl -sO https://vitna.costrinity.xyz/sample-evidence.json
node verify-evidence.mjs sample-evidence.jsonHow verification works, in two tiers
Tier 1, the evidence package. A detached Ed25519 signature covers the whole package. Anyone can verify it offline with our published public key, with no VITNA account and no VITNA secret. This is the tier that supports non-repudiation of issuance.
Tier 2, each individual record. Every decision record is committed by a sha256 hash over its canonical JSON, and that ordered list of hashes sits inside the Ed25519-signed package. A third party can therefore verify each record, not just the package: recompute the record's hash and confirm it matches at its position. The verifier does this automatically and reports PASS or FAIL per record.
Separately, each record is integrity protected at write time with HMAC-SHA256. That is an internal check, verifiable only by VITNA because HMAC is symmetric, so we never describe it as independently verifiable or as non-repudiation. Bundles exported before the per-record hashes shipped still verify at the package level, and the verifier says so.
Recomputing payload_sha256 (the pfa-v2 scheme)
Each decision record carries a payload_sha256 and a canon_version of pfa-v2. Here is the exact scheme so you can recompute it yourself. It is a sha256 (hex) over twelve fields joined with the pipe character, in this order, UTF-8 encoded, with no whitespace and no trailing separator. Null or absent values become the empty string.
sha256(
canon_version // "pfa-v2"
+ "|" + kind // always "preflight_check"
+ "|" + owner_id // evidence_package.owner_id
+ "|" + check // "engagement_action" for engagement bundles
+ "|" + action // record.action, "" if null
+ "|" + category // engagement: evidence_package.session_id
+ "|" + decision // record.decision
+ "|" + flagged // "1" if decision !== "allow", else "0"
+ "|" + reason // record.reason, "" if null
+ "|" + principal_id // "" for engagement bundles
+ "|" + effect // record.effect
+ "|" + signed_at // record.signed_at
)Worked example, taken verbatim from the published sample-evidence.json (record 0). Canonical string:
pfa-v2|preflight_check|f46ba5dc-b77b-4fe0-ae3d-55e6204e3d66|engagement_action|dns.read example.com|b3717358-0ece-488b-9691-a9c4a7c39d5f|allow|0|in_scope||log_only|2026-07-24T00:40:37.048Z
sha256 -> 2b0f0d22a1a3cb4980981a12e67fa72e778ca3b0d21d322ca90eac1f578e1b2fThat matches the payload_sha256 on record 0 of the published sample. Note the two consecutive pipes before log_only: that is the empty principal_id.
payload_sha256 is a digest, not a signature, so recomputing it proves the record fields are internally consistent, not that VITNA issued them. The per-record assurance that a third party can rely on is record_hashes, because those sit inside the Ed25519-signed package. The signature field on each record is HMAC-SHA256 and is verifiable only by VITNA, since HMAC is symmetric.Key transparency
The signing key is Ed25519, key_id 01833acd46d06ab4. The public key is published in more than one place, including infrastructure we do not control, so the copies can be compared:
- vitna.costrinity.xyz/api/evidence/pubkey (our API)
- PUBKEY.md in the public GitHub mirror (GitHub, not our infrastructure)
- The README of the npm package (npm registry, not our infrastructure)
- Embedded directly in
verify-evidence.mjs, so the verifier does not fetch a key at runtime
Because the verifier ships with the key embedded and the same key is published on GitHub and npm, a mismatch between any of those copies and our API would be publicly visible. We are stating the honest limit here too: this is multi-location publication, not a formal transparency log or a third-party notary.
What VITNA sends and stores
Connecting the MCP server sends data to VITNA's cloud. For a compliance product that has to be explicit, so here it is.
What is sent on a check
Whatever you pass to the tool. For action_preflight that is the proposed action text, an optional structured payload, and an optional short action-type label, plus your owner id and API key in headers. For the compliance checks it is the fields that check needs, for example purpose and data categories.
What is stored
Less than what is sent. The signed decision record stores the action type label, the threat category and the pattern name that matched, the verdict and reason, timestamps, and owner and session identifiers. The raw action text and raw payload are not stored. Data principals are referenced by UUID only, and detected secrets and PII are redacted before anything is written.
Retention by tier, against the six-month baseline
We cite the EU AI Act requirement to retain automatically generated logs for at least six months. Stated plainly, here is which tiers meet that on their own and which do not:
No tier below Enterprise retains event payloads in VITNA for six months, so do not rely on our hosted retention alone to satisfy a six-month obligation. The way any tier satisfies it is by exporting the Ed25519-signed evidence package and keeping it yourself. Once exported it is a self-contained, independently verifiable artifact that does not depend on our retention or on our servers continuing to exist.
Trial keys, and the zero-cloud alternative
With no credentials, the first MCP tool call self-provisions a restricted trial key against our API and caches it at ~/.vitna/credentials.json. That creates an account on our side. Trial keys are capped, return label-only results, and do not persist signed evidence until the account is claimed.
If you do not want events leaving your machine, use local mode: with storage_mode=local events stay on the operator's device and VITNA stores nothing server-side. The desktop app is the packaged form of that. The tradeoff is honest: in local mode we cannot produce a server-signed evidence package for you, because signing happens where the key is.
FAQ
Is VITNA a verifiable compliance receipt system?
Yes, for the signed evidence package model. Each checked action produces a decision record, and those records are packaged into an Ed25519-signed evidence package that functions as a verifiable compliance receipt: a third party can confirm offline, with our published public key and an open-source verifier, that the receipt was issued by VITNA, has not been altered, and that each individual record matches its committed hash. The documented limits still apply. Detection is heuristic pattern matching, VITNA is a cooperative guardrail the agent calls rather than an enforcement sandbox, and a valid receipt proves an action was checked and what the verdict was, not that the action was actually performed or that the underlying records are factually true.
Can I verify a single decision record on its own?
Yes. Every record is committed by sha256 over its canonical JSON inside the Ed25519-signed package, so you recompute that hash and confirm it appears at the right position in the signed list. The bundled verifier does this for you and prints PASS or FAIL per record.
Does VITNA block dangerous actions?
No, and we will not claim otherwise. VITNA evaluates and records, and returns allow, block, or hold. Your system enforces the decision out of process. Its purpose is not prevention, it is independently verifiable proof that an action was checked and what was decided.