api reference
Intended Documentation
API Reference: Audit
Query the per-tenant SHA-256 audit chain, verify its integrity, fetch offline-verifiable decision receipts that name the rule-set version behind each decision, and export HMAC-signed evidence bundles.
Audit API#
Every state-changing runtime operation appends to a per-tenant, SHA-256 hash-chained audit log as a side effect — you do not opt in. Each entry stores the hash of the previous entry, so any later tampering breaks the chain and is detectable. This API lets you query that chain, verify its integrity, and export a signed evidence bundle for a single intent.
Each tenant has its own hash chain and its own signing key. Audit entries never cross the tenant boundary, and evidence is signed with the tenant's own secret.
What the chain stores#
The audit-grade substrate is the AuditEntry record: { id, tenantId, correlationId, intentId, actorId, actorType, eventType, payload, previousHash, entryHash, createdAt }, unique on (tenantId, entryHash). The entryHash is a SHA-256 over the canonical, key-sorted JSON of the entry, and previousHash links to the prior entry's hash (the chain head's previousHash is null). Appends run inside a serializable transaction. There are 40+ eventTypes — INTENT_RECEIVED, AUTHORITY_DECISION, TOKEN_ISSUED, EXECUTION_ATTEMPTED, EXECUTION_RESULT, ESCALATION_EVENT, and so on.
Info
A single POST /intent typically writes several entries (received → decision → token/execution → result). The audit.entriesWritten and audit.latestHash in the /intent response tell you how many were appended and the resulting chain head.
Query Tenant Audit Events#
Response: { tenantId, total, limit, offset, events: [...] }. Each event carries its eventType, payload, correlationId, entryHash, previousHash, and createdAt.
Verify Chain Integrity#
Response:
If the chain is broken, valid is false and brokenAt is the index of the first failing entry:
Info
A cross-tenant sweep, POST /audit/verify-chain, runs the same verification across every tenant and raises an ESCALATION_EVENT alert on any broken chain. Note a known reporting quirk: its per-tenant checkedRecords field is always 0 (the underlying verifier returns totalEntries, not checkedRecords). The valid / brokenAt results are correct; only that count is wrong. The single-tenant endpoint above is unaffected.
Generate an Evidence Bundle#
Response (EvidenceBundle):
| Field | Notes |
|---|---|
integrityHash | sha256:<hex> over the serialized events. |
signature | hmac-sha256:<hex> over intentId:integrityHash, keyed with the tenant's own stored secret. |
chainValid | Whether the underlying chain verified at generation time. |
Warning
On what "signed" means here. The evidence signature is a symmetric HMAC-SHA256 keyed with the tenant's own secret. Verifying it (verifyEvidenceBundle, with timing-safe comparison) requires possession of that same secret. It is therefore not publicly or asymmetrically verifiable the way the Ed25519 decision token is — a third party cannot independently verify the bundle without the tenant key. State this precisely in any compliance claim.
| Status | Code | Cause |
|---|---|---|
400 | VALIDATION_ERROR | Bad path params. |
404 | NO_AUTHORITY_KEY | Tenant has no ACTIVE signing key. |
404 | NO_AUDIT_EVENTS | No audit events for that intent. |
500 | EVIDENCE_BUNDLE_FAILED | Generation failed closed. |
Get a Decision Receipt#
A receipt carries the outcome, the risk score, the decision time, this intent's audit entries as a hash-linked chain, the authority token (on ALLOW only), the tenant's public keys, a provenance object, and a receiptHash over all of it.
provenance records which classifier and which rule-set version made the decision:
| Field | Notes |
|---|---|
classifierBackend | The classifier that ran: rules, llm-api, local-model, structured-goal-rules (Physical AI), or none when the decision path ran no classifier: /gateway/evaluate and /authority/evaluate take an already-structured intent. |
classifierVersion | The classifier build: Open Intent taxonomy version, a hash of the intent library and the engine version. null when no classifier ran. |
knowledgePackVersionId | The knowledge pack the decision consulted, or null. |
policyVersion | The rule set that decided, as <policySetId>@v<version> (comma-separated when several sets were evaluated), fallback-bootstrap@v3 for the starter policy (the version changes when the starter rules change), or physical-policy@v<version> / physical-default@v1 for Physical AI. |
policyHash | SHA-256 over the exact rules evaluated, including the always-on catastrophic-action floor when it applies. Any rule change gives a new hash. |
rulesetVersion | The signed classifier ruleset release that classified the action, for example oi-rules-1.2.0. Present only when the rules classifier ran. |
rulesetHash | SHA-256 content hash of that ruleset release (sha256:<hex>). Present only together with rulesetVersion. |
Any SDK from 0.6.0 on verifies receipts that carry rulesetVersion and rulesetHash, because the check compares the whole provenance object with the sealed copy.
A field is null when it was not recorded. Receipts for decisions made before provenance was recorded have no provenance object.
The provenance is also sealed as the last entry of the receipt's chain (id: "decision-provenance"), so receiptHash covers it. Check a receipt with the SDK (Node.js):
In code, verifyReceipt from @intended-inc/sdk/receipt checks the receipt and readReceiptProvenance returns the sealed provenance. Verification fails with RECEIPT_TAMPERED or RECEIPT_CHAIN_BROKEN if the sealed provenance was changed, and with RECEIPT_PROVENANCE_MISMATCH if the top-level provenance object does not match the sealed copy.
Info
SDK versions before 0.6.0 still verify receipts that carry provenance, because the provenance is part of the hash-linked chain. They do not compare the top-level provenance object with the sealed copy, so use SDK 0.6.0 or later, or read the chain entry, when you rely on it.
| Status | Code | Cause |
|---|---|---|
404 | INTENT_NOT_FOUND | No intent with that id in your tenant. |
409 | RECEIPT_UNAVAILABLE | No decision is recorded for the intent yet, or an ALLOW has no stored authority token. |
Run-Stream Endpoints#
The runtime also keeps an unsigned, unchained narrative stream (AuditRun / AuditRunEvent) for replay and export, distinct from the tamper-evident chain above.
| Method | Path | Purpose |
|---|---|---|
| GET | /audit/run/:runId?tenantId=… | Fetch the run's event stream. |
| GET | /audit/run/:runId/replay?tenantId=… | Replay the run deterministically. |
| GET | /audit/run/:runId/export?tenantId=… | Export the run's narrative. |
| GET | /audit/:tenantId/timeline?limit=… | Executive-readable timeline (limit 1–200, default 100). |
Info
The narrative stream is for human review and replay; it is not the tamper-evident substrate. Tamper-evidence comes from the hash-chained AuditEntry log queried above. Keep the two distinct in any audit narrative.
Enterprise Export Jobs#
For formal, asynchronous compliance exports, use the admin export jobs. These require the enterprise audit:read permission and resolve through the tenant boundary.
| Method | Path | Purpose |
|---|---|---|
| POST | /admin/audit-exports | Create an export (201). |
| GET | /admin/audit-exports?tenantId=… | List exports. |
| GET | /admin/audit-exports/:id?tenantId=… | Export status. |
| GET | /admin/audit-exports/:id/download?tenantId=… | Download the artifact (404 AUDIT_EXPORT_ARTIFACT_NOT_READY until ready). |
The download response carries the artifact plus x-intended-artifact-digest and x-intended-artifact-signature headers.
Warning
The system provides the mechanisms of audit — an immutable hash chain, signed export, and tamper detection — but it encodes no compliance-framework control IDs (no SOC 2, ISO 27001, or EU AI Act mappings live in code). Do not represent these endpoints as a certified control mapping; represent them as the evidence substrate an auditor can build on.
Next steps#
- Audit Export — the operator workflow for evidence exports
- Intents API — the calls that write to this chain
- Tenant Trust Boundary — why each tenant's chain and keys are isolated