Seal governed AI-agent decisions into tamper-evident, Ed25519-signed records — and verify them offline, against public infrastructure, with zero dependency on VDA. Three ways in: SDK, MCP, raw REST. OpenAPI spec →
Fastest first success needs no key — verify is public (skip to Verify). To seal, get a key:
instant · no human in the loop Sealed-tier key (one click on the home page, or:)
curl -X POST https://witness.getvda.ai/api/witness/test-key -H "content-type: application/json" -d '{"email":"you@example.com"}'
# -> { "apiKey":"wtn.xxxx.yyyy", "accountId":"acct_...", "tier":"test", "compliance":false, "expiresInDays":7 }
Autonomous agents: this is discoverable in-band — the A2A card's provisioning.selfServeKey field and the MCP get_test_key tool point here, so an agent can bootstrap a key with no homepage scraping.
Every response exposes sealed (always true), anchored (true/false), and sealedState: anchored · anchoring_pending (Anchored tier, the next hourly tick commits it) · not_anchored (Sealed tier, terminal — reason anchoring_not_enabled_for_account). The three-state verdict (ANCHORED_VALID/SIGNED_PENDING/BROKEN) is unchanged; tier/compliance are deprecated aliases.
Use the key as a bearer token: Authorization: Bearer <key> (the x-witness-key: <key> header also works). Rotate/replace anytime.
This is the way to provision, not an upgrade to the quick-start key above: bind a controller key you hold, then mint your own short-lived keys by renewing. An API key is a short-lived credential; the account is the durable identity that outlives it — so there is no standing credential to leak, and an agent re-keys its OWN account indefinitely with no human and no permanent secret. Concierge/service provisioning goes further: supply a controller and no bootstrap key is issued at all — your first key comes from a renewal (apiKey:null at creation). A returned key with no controller is a quick-start exception; a non-expiring key is an explicit, audited exception — neither is the norm.
1. Claim a durable account — generate an Ed25519 keypair, send the public JWK:
curl -X POST https://witness.getvda.ai/api/witness/test-key -H "content-type: application/json" \
-d '{"controllerPublicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"<base64url>"}}'
# -> { "apiKey":"wtn...", "accountId":"acct_...", "durable":true, "controllerBound":true, "keyExpiresAt":"...", "keyTtlSec":86400 }
The account is now durable (no 7-day reaper); the key is short-lived (~24h). Already hold a legacy key? Bind a controller to that existing account: POST https://witness.getvda.ai/api/witness/account/bind-controller (auth: your current key) with the same JWK.
2. Renew unattended when the key expires — same account, same chain:
# a. get a challenge nonce
curl -X POST https://witness.getvda.ai/api/witness/renew/challenge -H "content-type: application/json" -d '{"accountId":"acct_..."}'
# -> { "nonce":"...", "expiresInSec":300 }
# b. Ed25519-sign the exact string: vda.witness.renew/1|<accountId>|<nonce> with your controller PRIVATE key
# c. exchange the signature for a fresh key
curl -X POST https://witness.getvda.ai/api/witness/renew -H "content-type: application/json" \
-d '{"accountId":"acct_...","nonce":"...","signature":"<base64url>"}'
# -> { "apiKey":"wtn...(fresh)", "accountId":"acct_...(same)", "keyExpiresAt":"...", "keyTtlSec":86400 }
Prior keys stay valid until they expire (overlap = zero-downtime rotation). Renewal is authorised by your controller-key signature, rate-limited per account, and not subject to the anonymous-mint caps — you proved you own the account. Also discoverable in-band: agent card provisioning.renewKey + MCP tools renew_challenge / renew_key.
seq with an unbroken prev-hash. Upgrading a test account to anchored (compliance) later preserves the same account id and chains — earlier records stay honestly labelled non-anchored.npm i vda-witness # or: pip install "vda-witness[verify]"
import { offlineVerify } from "vda-witness/verify";
const v = await offlineVerify({ record, chain, didDocument, anchor }); // "bundled" | "live-public"
// v.state === "ANCHORED_VALID" | "SIGNED_PENDING" | "BROKEN" | "INSUFFICIENT_PROOF"
curl "https://witness.getvda.ai/api/witness/records/<recordId>?proof=chain" -H "authorization: Bearer $KEY"
# -> { record, chain:[genesis..head], anchor:{rekor SET + TSA tokens}, didDocument, proofType:"hash-chain-predecessor-path" }
curl "https://witness.getvda.ai/api/witness/chains/<chainKey>/proof" -H "authorization: Bearer $KEY" # whole trail in one pass
Feed {record, chain, anchor, didDocument} to offlineVerify — it checks signature + key-at-signing + chain continuity + anchor, never calling witness.getvda.ai. INSUFFICIENT_PROOF means the bundle was incomplete (missing predecessors) — it is not tampering; BROKEN/chain is reserved for a genuinely altered/removed/reordered chain. Size is linear in chain length (predecessor path to the anchored head), not a Merkle inclusion proof.curl -X POST https://witness.getvda.ai/api/witness/verify -H "content-type: application/json" \
-d '{"record": { ...a witness record... }}'
# -> { "ok": true, "bodyHash": "sha256:...", "signatureValid": true, "errors": [] }
The verify tool needs no key — a connector is useful read-only before you add one. See MCP connectors.
import { Witness } from "vda-witness";
const witness = new Witness({ apiKey: process.env.WITNESS_API_KEY });
await witness.seal(
{ agent: "Refund Agent", inputs: { amountEur: 150 }, verdict: "PASS", reasoning: "<= 200, good standing" },
{ ruleId: "refund.auto", ruleText: "Agents MAY auto-approve refunds up to EUR200." },
); // fail-open: never throws, never blocks your agent
curl -X POST https://witness.getvda.ai/api/witness/seal \
-H "authorization: Bearer $WITNESS_API_KEY" -H "content-type: application/json" \
-d '{
"decision": { "agent":"Refund Agent", "inputs":{"amountEur":150}, "verdict":"PASS", "reasoning":"<= 200, good standing" },
"governingRule": { "ruleId":"refund.auto", "ruleText":"Agents MAY auto-approve refunds up to EUR200." },
"chainKey": "default",
"decisionId": "optional-idempotency-key"
}'
# -> { "record": {...signed body + proof...}, "bodyHash":"sha256:...", "stored":true, "chainKey":"default" }
The account is derived from your credential and stamped (signed) into the record — you cannot send an account field (it is rejected).
Prefer these over the generic seal. Witness is opinionated about evidentiary completeness — is a record reconstructable by an auditor? — but never about whether a decision was correct. Each shaped skill requires the fields that make its record self-reconstructable; the generic seal above is the compatibility escape (its records carry no record_type and report as unstructured). All three produce a standard vda.witness.record/1 — the structured block rides inside the signed decision.inputs, so it is tamper-evident today with no schema change, readable via get_record and surfaced as recordType in list_records.
agent_context) — it does not independently authenticate them. The record proves what was claimed, cryptographically bound and tamper-evident; it does not prove the claim of identity itself.seal_hitl_decision — a human decidedCaptures the deciding human, the disposition + rationale, the policies cited as the basis, and content-addressed references (with sha256 hashes) to the system-of-record artifacts the decider saw at decision time — not the action produced. That is what makes the trail auditor-reconstructable.
curl -X POST https://witness.getvda.ai/api/witness/seal/hitl-decision -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"actor": {"id":"jane.ops@stay","type":"human","role":"duty-manager"},
"decision": {"disposition":"approved","statement":"Waived late-cancel fee; guest showed a flight-cancellation notice."},
"governing_clauses": [{"ref":"SOP.cancellations#3.2","text":"Duty managers MAY waive late-cancel fees on documented disruption.","hash":"sha256:..."}],
"evidence": [
{"ref":"pms://reservation/RES-88421","hash":"sha256:...","media_type":"application/json","captured_at":"2026-07-15T09:41:00Z"},
{"ref":"upload://flight-cancel-notice.pdf","hash":"sha256:...","inline":{"encoding":"base64","bytes":"..."}}
],
"basis_captured_at": "2026-07-15T09:41:00Z"
}'
# evidence is what the decider SAW (external, hashed). No basis? give "evidence_omitted_reason". inline <=100KB, hash-verified.
seal_agent_action — an agent acted autonomouslyRecords what the agent consumed, split by provenance: evidence is external material it saw (content-addressed + hashed); parameters is the computed arguments it was passed (self-contained, no hash). One-question test: does it exist outside this record? → evidence (hash it); did you compute/pass it as an argument? → parameters. At least one is required (or evidence_omitted_reason). Optional agent_context carries execution-substrate hints so an auditor can ask "was this at a known-buggy revision?"
curl -X POST https://witness.getvda.ai/api/witness/seal/agent-action -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"actor": {"id":"c2md-classifier","type":"agent"},
"action": {"statement":"Classified the agent as high-risk under EU AI Act Annex III.","outcome":"high_risk"},
"governing_rule": {"ref":"eu-ai-act#annex-III"},
"evidence": [{"ref":"witness://record/rec_...","hash":"sha256:...","description":"whoami resolution for the credential"}],
"parameters": {"jurisdictions":["EU"],"data_categories":["biometric"]},
"agent_context": {"cloud_run_revision":"c2md-api-00042-abc","model_name":"gemini-2.5-flash","region":"europe-west1"}
}'
seal_attestation — assert a fact/state as of a timeFor "model X passed eval Y", "card issued", "key rotated", "config was live" — not for decisions. Supporting evidence and a governing_basis framework are optional; without a basis it is sealed as attested-by-the-signing-party, not independently verified.
curl -X POST https://witness.getvda.ai/api/witness/seal/attestation -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"actor": {"id":"c2md-issuer","type":"system"},
"claim": "Issued agent card did:web:example#key-3 to tenant acct_...",
"as_of": "2026-07-15T10:00:00Z"
}'
A shaped seal that is missing a required field returns 400 with a message naming the gap — the schema is the guidance. Existing seal callers are unaffected; these are additive.
An admission credential is a sealed attestation that admits an agent to a customer environment — the Witness record IS the credential, not a separate document. The issuing service (e.g. the Onboarding Agent) seals it; enforcers hold only the credential_id and check it by calling Witness. Records are immutable: validity is computed at verify time, and revocation is a new superseding record. Under the hood it stays record_type: attestation with attestation_type: "admission_credential" — no new envelope type.
subject_did, not that the party presenting it is that subject. Enforcers MUST separately challenge the presenter to prove control of subject_did (a DID challenge-response), exactly as you would not accept a certificate serial number as proof of identity.curl -X POST https://witness.getvda.ai/api/witness/credentials/issue -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"subject_did":"did:web:agent.example.com","issuer_did":"did:web:onboard.getvda.ai",
"environment_id":"env_citizenm_prod","scope":["reservation.read","folio.settle"],
"governance_files_hash":"sha256:...","sandbox_result":{"pass":true,"score":0.97,"evidence_seal_ref":"rec_..."},
"impact_delta_ref":"rec_...","expires_at":"2027-07-16T00:00:00Z",
"compliance_mappings":[{"framework":"eu_ai_act","article_ref":"Article 13","claim":"..."}]
}'
# -> { "credential_id":"rec_...", "record":{...signed...}, "stored":true }
curl "https://witness.getvda.ai/api/witness/credentials/<credential_id>"
# -> { "valid":true, "code":"valid", "subject":"did:web:agent...", "issuer":"did:web:onboard.getvda.ai",
# "environment":"env_citizenm_prod", "scope":["reservation.read"], "expires_at":"...", "revoked":false }
# code is one of: valid | revoked | expired | not_found | not_credential (always HTTP 200 — branch on code)
# revoked -> { "valid":false, "code":"revoked", "subject":"...", "environment":"...", "revoked":true, "revoked_at":"...", "reason_code":"compromised" }
Witness computes validity as a single canonical answer (issued ∧ signature verifies ∧ not revoked by the issuer ∧ not expired), so every enforcer is identically correct and the definition of "valid" evolves in one place — never drifting across independent enforcer implementations. Cache-Control: private, max-age=60 for credential-found states (revocations propagate within 60s); no-store for not_found / not_credential.
curl -X POST https://witness.getvda.ai/api/witness/credentials/<credential_id>/revoke -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"reason_code":"compromised","revoked_by":"did:web:onboard.getvda.ai","reason_text":"Out-of-band key rotation."
}'
Only the account that issued a credential may revoke it (enforced structurally, not by a DID string). Revocation is terminal — re-admission is a new credential. Customers who want a credential revoked call the issuing service (which owns revocation policy), not Witness directly.
By default the shaped skills seal custodially — Witness signs with its platform key. That is tamper-evident and anchored, but the signature is Witness's. For issuer-authenticity provable even against Witness — the property that matters for a credential-issuing authority — seal customer-managed: the record is signed by your own key, and Witness never holds it. You keep shape enforcement and get issuer-authenticity, because Witness stays the sole assembler (no client-side body-builder to drift).
Three steps — prepare → sign → submit:
# 1. PREPARE (stateless — Witness assembles, stores nothing; returns the bytes to sign)
curl -X POST https://witness.getvda.ai/api/witness/prepare -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"skill":"issue_admission_credential",
"params":{ ...the admission_credential params... },
"signingPublicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"<your record-signing PUBLIC key>"}
}'
# -> { "record":{...unsigned...}, "canonicalBytes":"<exact UTF-8 to sign>", "seq":N, "prevHash":"..." }
# 2. SIGN canonicalBytes with your record-signing PRIVATE key: raw Ed25519, signature base64url.
# 3. SUBMIT the signed record to the skill's normal endpoint
curl -X POST https://witness.getvda.ai/api/witness/credentials/issue -H "authorization: Bearer $WITNESS_API_KEY" -d '{
"record":{ ...the prepared record..., "proof":{"algorithm":"Ed25519","signature":"<base64url>","created":"<record.issuedAt>"} }
}'
# custody = customer-managed. If the chain advanced between prepare and submit -> 409, re-prepare.
Same flow for revoke_admission_credential, seal_agent_action, seal_attestation, seal_hitl_decision (change skill + submit endpoint).
check_valid returns issuer_verified as a signal distinct from the lifecycle verdict. To get issuer_verified: true, publish your record-signing public key at your issuer's DID document (e.g. did:web:onboard.getvda.ai → /.well-known/did.json). Then Witness resolves issuer_did and confirms the record's signing key is published there:
| issuer_verification | meaning |
|---|---|
verified | customer-managed + signing key is in the issuer's DID doc — provable even against Witness |
key_not_in_did_doc | ⚠️ claims an issuer but signed by a key not in its DID doc — suspicious, do not ignore |
custodial | Witness-signed — issuer-authenticity not established (integrity/anchoring still hold) |
did_unresolvable | the DID couldn't be resolved — the check did not complete (not verified, not forged) |
verify_record_issuer)Publicly checkable issuer-authenticity for any record by id, without exposing it. Answers "is this record's signature by the issuer it claims?" and returns only a verdict, the issuer DID, and the key it resolved against — never decision.inputs, evidence, or body content. This is how an enforcer of a privilege-widening event — e.g. a service sealing its own genesis authority registration or a baseline promotion, customer-managed — confirms the issuer signed it, not merely that Witness recorded it, without holding the account's key.
curl "https://witness.getvda.ai/api/witness/records/<recordId>/issuer" # PUBLIC — no key
# -> { "record_id":"rec_...", "signature_valid":true, "issuer_verified":true,
# "issuer_verification":"verified", "issuer_did":"did:web:hitl.getvda.ai",
# "signer_key":{"kty":"OKP","crv":"Ed25519","x":"..."} }
Same four states as check_valid's issuer_verified: verified / key_not_in_did_doc (loud) / custodial (not applicable — a different custody model, not a failure) / did_unresolvable (incomplete check). The issuer DID comes from the credential's issuer_did or, for a general record, the signer's did:web keyId. Also via MCP: verify_record_issuer.
revoke_api_key)A revoked key stops authenticating at once, and the revocation is itself sealed as a key_revocation attestation on your chain. Two authorities, distinguished by revoked_by.type (the trust level, not a code path):
Controller-authorized (owner self-service, no operator) — prepare a key_revocation and sign it with your bound controller key, so the record is customer-managed and provable against Witness:
# 1. prepare -> 2. Ed25519-sign canonicalBytes with your controller key -> 3. submit:
curl -X POST "https://witness.getvda.ai/api/witness/keys/revoke" -H "authorization: Bearer $WITNESS_API_KEY" \
-d '{"record":{ ...prepared key_revocation..., "proof":{"algorithm":"Ed25519","signature":"..."} }}'
# -> { "revoked": true, "key_id": "<keyId>", "revoked_by": { "type": "controller" } }
Admin break-glass (operator) — POST https://witness.getvda.ai/api/witness/keys/revoke {"key_id":"..."} with the x-witness-admin header; sealed custodially, revoked_by.type="operator". Revocation is total: a revoked key 401s and its account's records become unfetchable with it — there is no operator backdoor. Reflected in whoami (revoked key 401s; only a sibling's ≤60s cache is a lag).
An operational query over your trail — account-scoped by your key (never an accountId param). Distinct from the Article-12 report, which is an evidence artefact.
curl "https://witness.getvda.ai/api/witness/records?limit=50&chainKey=default" -H "authorization: Bearer $WITNESS_API_KEY"
# -> { "account":"acct_...", "chainKeys":["default","..."], // discover your own chains
# "records":[ { "recordId":"...", "chainKey":"default", "seq":0, "verdict":"PASS",
# "agent":"Refund Agent", "ruleId":"refund.auto", "bodyHash":"sha256:...",
# "sealedAt":"...", "anchorState":"SIGNED_PENDING", "compliance":false } ],
# "nextCursor":null }
# filters: chainKey, since/until (ISO-8601), limit (1-500), cursor. ?view=full for full bodies.
curl "https://witness.getvda.ai/api/witness/records/<recordId>" -H "authorization: Bearer $WITNESS_API_KEY"
# -> { "record": { ...decision{agent,verdict,reasoning,inputs,actionProposed},
# governingRule{ruleId,ruleText}, seq, prevHash, proof... },
# "chainKey":"default", "bodyHash":"sha256:...", "signatureValid":true,
# "anchorState":"ANCHORED_VALID", "anchor": { "head":"sha256:...", "seq":0 } }
Everything needed to read back your reasoning and verify the record offline. A foreign or unknown recordId returns 404 not-found — indistinguishable from nonexistent (no existence leak). Also via MCP: list_records, get_record.
whoami)For other services in the getvda.ai suite (C2MD first). When a sibling service accepts Authorization: Bearer wtn.<id>.<secret> from its caller, it validates that key by asking Witness — because Witness is the single source of truth for its own keys (no sibling replicates the key store).
curl "https://witness.getvda.ai/api/witness/whoami" -H "authorization: Bearer wtn.xxxx.yyyy"
# -> { "account_id":"acct_...", "tier":"SEALED"|"ANCHORED", "scopes":["seal","read"],
# "compliance":false, "key_id":"xxxx", "revoked":false }
# 200: Cache-Control: private, max-age=60 · 401 (generic, no-store) on any bad/unknown/revoked key
tier is resolved from account state (ANCHORED on the anchored tier, else SEALED) — the key identifies the credential; the account carries the tier. Any valid key may call whoami about itself (no elevated scope) and can only ever ask about the key it presents, so no other account is enumerable. There is no privileged service credential — a sibling acts on behalf of whichever end-user key its own caller presented. 401s (unknown / malformed / wrong-secret / revoked) return one generic message and are never cached, so a revocation propagates within the ~1-minute cache window. Permissively rate-limited per calling IP and per account.
Three tiers, in the Sealed/Anchored vocabulary:
seal response (a usage block) and via GET /api/witness/usage, but never enforced: over the 5,000/month allowance sealing continues (warns, never blocks — an evidence trail is never silently dropped). Upgrading Sealed → Anchored preserves your account id and all chains (anchoringBeganSeq records where anchoring began); earlier records stay honestly sealed-not-anchored.curl "https://witness.getvda.ai/api/witness/usage" -H "authorization: Bearer $WITNESS_API_KEY"
# -> { "account":"acct_...", "usage": { "seals_this_period":42, "included":5000, "remaining":4958,
# "period":"YYYY-MM", "period_end":"...", "tier":"sealed", "over_limit":false,
# "enforcement":"measured_not_billed" } }
# The same usage block rides on every /api/witness/seal response.
Endpoint: https://witness.getvda.ai/api/witness/mcp · Tools: get_test_key, renew_challenge, renew_key, seal_hitl_decision, seal_agent_action, seal_attestation, issue_admission_credential, check_valid, revoke_admission_credential, whoami, seal, list_records, get_record, verify, report · Auth: API key as a bearer header (no OAuth). verify, check_valid, get_test_key, and the renew_* tools are keyless.
claude_desktop_config.json{
"mcpServers": {
"vda-witness": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://witness.getvda.ai/api/witness/mcp",
"--header", "Authorization: Bearer ${WITNESS_API_KEY}"],
"env": { "WITNESS_API_KEY": "wtn.xxxx.yyyy" }
}
}
}
Omit the --header/env to run read-only (verify only).
Settings → Connectors → Add custom connector → URL https://witness.getvda.ai/api/witness/mcp. When prompted for a header, add Authorization: Bearer <your key>. Leave blank for keyless verify.
~/.cursor/mcp.json{ "mcpServers": { "vda-witness": {
"url": "https://witness.getvda.ai/api/witness/mcp",
"headers": { "Authorization": "Bearer wtn.xxxx.yyyy" }
} } }
.vscode/mcp.json{ "servers": { "vda-witness": {
"type": "http", "url": "https://witness.getvda.ai/api/witness/mcp",
"headers": { "Authorization": "Bearer wtn.xxxx.yyyy" }
} } }
decision| field | type | notes |
|---|---|---|
| agent | string | required · which agent decided |
| inputs | any JSON | the decision inputs (canonical ≤ 256KB) |
| verdict | string | required |
| reasoning | string | required |
| actionProposed | string | optional |
governingRule| field | type | notes |
|---|---|---|
| ruleId | string | required |
| ruleText | string | required · the rule verbatim/ref |
| governanceRef | string | optional · e.g. AGENTS.md#refunds |
| governanceHash | string | optional · sha256:… |
default; max 128 chars [A-Za-z0-9._:-]. This is also how you GROUP records — see below.decisionId; dedupe is on (account, decisionId): the same value returns the existing record with deduped:true (never cross-tenant).429 when exceeded. Test-key issuance is rate-limited per IP.There is no bundle_id / group_id / parent_id field, because chainKey already is one. Every seal endpoint takes an optional top-level chainKey; records sharing one are hash-linked in seal order into a set that is retrievable and provable as a set:
GET https://witness.getvda.ai/api/witness/chains/<chainKey>/proof # whole trail + anchor + did.json, one call
GET https://witness.getvda.ai/api/witness/records?chainKey=<key> # just that group (+ chainKeys[] to discover)
POST https://witness.getvda.ai/api/witness/report {"chainKey":"..."} # Article 12 evidence scoped to the group
Give the seals of one workflow, compliance bundle, or case a stable shared key, namespaced to you — c2md:bundle:<id>, acp:run:<id>. Omit it and everything lands on default.
evidence. An evidence hash is the content hash of external material — a record ID is not its content, so such a manifest either carries a hash an auditor cannot reproduce or fabricates one. The chain link is the grouping proof: cryptographic, Witness-verified, and free.Chains are cheap and independent. Each (account, chainKey) anchors on its own cursor, so a finished group anchors once and then goes quiet rather than re-anchoring forever behind an ever-growing chain — many small chains cost less than one large one. A dedicated chain also has a single writer, avoiding the 409 a customer-managed prepare/submit hits when another writer advances the chain first. Chains are account-scoped: a foreign or unknown chainKey returns 404, never a leak.
decisionId is not namespaced by chainKey. Dedupe is (account, decisionId), backed by a UNIQUE index that does not include chain_key — so the same decisionId on a different chain still collides and returns the first record with deduped:true. Never derive a decisionId from the chainKey alone for a record whose content may change; include a content hash or seq discriminator, or omit it.| code | meaning |
|---|---|
| 400 | bad request — e.g. you sent an account field (derived from your credential) |
| 401 | missing / invalid / revoked API key |
| 409 | seq conflict on the chain |
| 413 | payload too large |
| 429 | rate limited |
curl -X POST https://witness.getvda.ai/api/witness/report -H "authorization: Bearer $WITNESS_API_KEY"
# -> { "reportType":"vda.witness.art12-evidence/1", "lifecycle":"DEMO_DATA", "entries":[ ... one per sealed decision ... ] }
Also exposed as the MCP report tool. It is evidence, not a compliance certificate — Compliance Officer attestation is the human act that makes it a regulatory artefact.
SDKs: npm · PyPI (Apache-2.0; the PyPI sdist ships full source). Machine-readable: A2A agent card · OpenAPI · llms.txt.