Skip to main content
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:
FieldTypeDescription
idstringUnique identifier for this log entry
agentIdstringThe agent that made the request
actionstringThe action field from the authorization request
toolNamestringThe tool name that was evaluated
parametersobjectThe parameters passed with the tool call
result"allowed" | "denied" | "escalated"The outcome of the evaluation
policyIdstring | nullThe policy that produced this decision — null means default deny
reasonstringHuman-readable explanation of the decision
latencyMsnumberHow long the authorization check took in milliseconds
timestampstringISO 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.
FilterTypeDefaultDescription
agentIdstringFilter to a specific agent
actionstringFilter by action name
toolNamestringFilter by tool name
result"allowed" | "denied" | "escalated"Filter by outcome
fromstring (ISO 8601)Return entries at or after this time
tostring (ISO 8601)Return entries at or before this time
limitnumber100Maximum entries to return
offsetnumber0Number 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