Veto records every authorization decision it makes. The audit log gives you a complete, immutable trail of what your agents attempted and what Veto decided — useful for debugging policy misconfigurations, conducting security reviews, and satisfying compliance requirements.
What the audit log captures
Each entry in the audit log represents a single call to authorize(). It records:
| Field | Type | Description |
|---|
id | string | Unique identifier for this log entry |
agentId | string | The agent that made the request |
action | string | The action field from the authorization request |
toolName | string | The tool name that was evaluated |
parameters | object | The parameters passed with the tool call |
result | "allowed" | "denied" | "escalated" | The outcome of the evaluation |
policyId | string | null | The policy that produced this decision — null means default deny |
reason | string | Human-readable explanation of the decision |
latencyMs | number | How long the authorization check took in milliseconds |
timestamp | string | ISO 8601 timestamp of when the decision was made |
latencyMs tells you how fast each authorization check was processed. Veto targets sub-10ms decisions; high values may indicate network latency between your server and the Veto API.
Query the audit log with the SDK
Use veto.queryAuditLog() to fetch entries. Pass filter options to narrow the results.
All decisions for an agent
import { VetoClient } from "@useveto/node";
const veto = new VetoClient({ apiKey: process.env.VETO_API_KEY! });
const logs = await veto.queryAuditLog({ agentId: "agent-uuid" });
for (const entry of logs) {
console.log(`[${entry.timestamp}] ${entry.toolName} → ${entry.result}`);
console.log(` Reason: ${entry.reason}`);
console.log(` Policy: ${entry.policyId ?? "default deny"}`);
console.log(` Latency: ${entry.latencyMs}ms`);
}
Only denied actions in the last 24 hours
const denied = await veto.queryAuditLog({
agentId: "agent-uuid",
result: "denied",
from: new Date(Date.now() - 86400000).toISOString(),
to: new Date().toISOString(),
limit: 100,
offset: 0,
});
Filter reference
All filters are optional. When you omit a filter, it is not applied.
| Filter | Type | Default | Description |
|---|
agentId | string | — | Filter to a specific agent |
action | string | — | Filter by action name |
toolName | string | — | Filter by tool name |
result | "allowed" | "denied" | "escalated" | — | Filter by outcome |
from | string (ISO 8601) | — | Return entries at or after this time |
to | string (ISO 8601) | — | Return entries at or before this time |
limit | number | 100 | Maximum entries to return |
offset | number | 0 | Number of entries to skip (for pagination) |
Paginating through results
const pageSize = 100;
let offset = 0;
let page: Awaited<ReturnType<typeof veto.queryAuditLog>>;
do {
page = await veto.queryAuditLog({
agentId: "agent-uuid",
limit: pageSize,
offset,
});
for (const entry of page) {
// process entry
}
offset += pageSize;
} while (page.length === pageSize);
Query the audit log with the REST API
You can query the audit log directly via HTTP without the SDK. Pass filters as query parameters.
GET /v1/audit-logs?agent_id=agent-uuid&result=denied
curl https://api.veto.tools/v1/audit-logs \
-H "Authorization: Bearer $VETO_API_KEY" \
-G \
--data-urlencode "agent_id=agent-uuid" \
--data-urlencode "result=denied" \
--data-urlencode "from=2026-01-01T00:00:00Z" \
--data-urlencode "limit=50"
The response is a JSON array of AuditLogEntry objects.
Common use cases
Debugging policy misconfigurations
If your agent is being blocked unexpectedly, query for denied decisions and inspect policyId and reason:
const denied = await veto.queryAuditLog({
agentId: "agent-uuid",
result: "denied",
limit: 20,
});
for (const entry of denied) {
console.log(`Tool: ${entry.toolName}`);
console.log(`Reason: ${entry.reason}`);
console.log(`Matched policy: ${entry.policyId ?? "no policy — default deny"}`);
}
A policyId of null means no policy matched at all — the action was blocked by the default deny rule. This usually means the tool name in the authorize call doesn’t match what’s in your allowlist.
Security review
Filter to a specific tool and time window to review all access attempts:
const fileDeletes = await veto.queryAuditLog({
agentId: "agent-uuid",
toolName: "file.delete",
from: "2026-01-01T00:00:00Z",
to: "2026-02-01T00:00:00Z",
});
Compliance auditing
Export all decisions for a given agent over a reporting period. Use pagination to handle large volumes:
const from = "2026-01-01T00:00:00Z";
const to = "2026-04-01T00:00:00Z";
const allEntries = [];
let offset = 0;
while (true) {
const batch = await veto.queryAuditLog({
agentId: "agent-uuid",
from,
to,
limit: 100,
offset,
});
allEntries.push(...batch);
if (batch.length < 100) break;
offset += 100;
}
console.log(`Total decisions in period: ${allEntries.length}`);
What’s next