Skip to main content

Installation

npm install @useveto/node

Setup

import { VetoClient } from "@useveto/node";

const veto = new VetoClient({
  apiKey: process.env.VETO_API_KEY!, // required
  endpoint: "https://api.veto.tools",  // optional — default
  timeout: 5000,                       // optional — ms, default 5000
});

Authorization

veto.authorize is the primary method. It checks whether an agent is permitted to call a tool with the given parameters and returns an AuthorizationResult.
veto.authorize(agentId: string, toolName: string, parameters?: Record<string, unknown>): Promise<AuthorizationResult>
The AuthorizationResult shape:
FieldTypeDescription
allowedbooleanWhether the action is permitted.
outcome"allowed" | "denied"The full authorization outcome.
matchedPolicyIdstring | nullID of the policy that produced this decision, or null for default deny.
reasonstringHuman-readable explanation.
evaluatedAtstringISO timestamp of when the decision was made.
const result = await veto.authorize("support-bot", "send_email", {
  to: "user@example.com",
  subject: "Refund confirmation",
});

if (!result.allowed) {
  console.log(`Blocked: ${result.reason}`);
  return;
}

// proceed with sending the email
If no policy matches the agent and tool, Veto denies by default. You must explicitly create a policy that allows an action for it to be permitted.

Agent management

const agent = await veto.createAgent({
  name: "support-bot",
  description: "Customer support automation agent",
});
// returns Agent: { id, name, description, status, createdAt, updatedAt }

Policy management

Policies contain one or more rules that determine what an agent can do. Higher priority values are evaluated first.

Rule types

TypeDescription
tool_allowlistExplicitly allow the listed tools (glob patterns supported).
tool_denylistExplicitly block the listed tools.
parameter_constraintConstrain parameter values via regex, enum, or numeric range.
rate_limitLimit how many times a tool can be called per time window.
time_basedRestrict tool use to specific hours or days of the week.

createPolicy

const policy = await veto.createPolicy({
  agentId: "agent-uuid",
  name: "Allow safe read operations",
  priority: 10,
  enabled: true,
  rules: [
    {
      type: "tool_allowlist",
      tools: ["file.read", "web.search"],
    },
    {
      type: "tool_denylist",
      tools: ["file.delete", "system.exec"],
    },
    {
      type: "parameter_constraint",
      tools: ["file.read"],
      parameters: {
        path: {
          regex: "^/home/",   // must start with /home/
        },
      },
    },
    {
      type: "rate_limit",
      tools: ["web.search"],
      rateLimit: {
        maxCalls: 100,
        windowSeconds: 3600, // 100 calls per hour
      },
    },
    {
      type: "time_based",
      tools: ["send_email"],
      timeWindow: {
        allowedHours: [9, 10, 11, 12, 13, 14, 15, 16, 17], // business hours
        allowedDays: [1, 2, 3, 4, 5],                       // Monday–Friday
        timezone: "America/New_York",
      },
    },
  ],
});
// list all policies
const policies = await veto.listPolicies();

// filter by agent
const agentPolicies = await veto.listPolicies("agent-uuid");

Audit log

queryAuditLog returns a paginated list of every authorization decision Veto has made.
veto.queryAuditLog(filters?: AuditLogFilters): Promise<PaginatedAuditLogResponse>

AuditLogFilters

FieldTypeDescription
agentIdstringFilter by agent.
actionstringFilter by action string.
toolNamestringFilter by tool name.
result"allowed" | "denied"Filter by outcome.
fromstringISO timestamp — entries on or after.
tostringISO timestamp — entries on or before.
limitnumberMax entries to return.
offsetnumberPagination offset.
const log = await veto.queryAuditLog({
  agentId: "agent-uuid",
  result: "denied",
  from: "2026-01-01T00:00:00Z",
  limit: 50,
  offset: 0,
});

console.log(`${log.pagination.total} total denials`);
for (const entry of log.data) {
  console.log(`[${entry.timestamp}] ${entry.toolName}${entry.reason}`);
}
The response also includes a pagination object with limit, offset, count, and total fields.

Error handling

The SDK exports three error classes:
ClassExtendsCodeStatusExtra field
VetoErrorErrorvariesvaries.code: string, .statusCode: number
UnauthorizedErrorVetoErrorUNAUTHORIZED401
RateLimitErrorVetoErrorRATE_LIMITED429.retryAfterMs: number
import { VetoError, UnauthorizedError, RateLimitError } from "@useveto/node";

try {
  const result = await veto.authorize("support-bot", "send_email", {
    to: "user@example.com",
  });

  if (!result.allowed) {
    console.warn(`Denied: ${result.reason}`);
  }
} catch (error) {
  if (error instanceof RateLimitError) {
    // back off and retry
    console.error(`Rate limited — retry after ${error.retryAfterMs}ms`);
    await sleep(error.retryAfterMs);
  } else if (error instanceof UnauthorizedError) {
    // bad or expired API key
    console.error("Invalid API key — check VETO_API_KEY");
  } else if (error instanceof VetoError) {
    // any other Veto error (timeout, network, server error)
    console.error(`Veto error [${error.code}]: ${error.message}`);
  }
}
VetoError also covers TIMEOUT (status 408) and NETWORK_ERROR (status 0) when the Veto API is unreachable.

Fail-closed behavior

By default, if the Veto API is unreachable (network error or timeout), the SDK denies the request. This ensures your agent cannot take unauthorized actions simply because authorization is unavailable. When using MCP middleware (createVetoGuard or vetoMiddleware), you can override this with the onError option:
import { VetoClient, createVetoGuard } from "@useveto/node";

const veto = new VetoClient({ apiKey: process.env.VETO_API_KEY! });

// Default — fail-closed (recommended for production)
const protect = createVetoGuard(veto, {
  agentId: "my-agent",
  onError: "deny",
});

// Fail-open — allow tool calls even when Veto is unreachable
const protect = createVetoGuard(veto, {
  agentId: "my-agent",
  onError: "allow",
});
onError: "allow" lets tool calls proceed when Veto is unreachable. This is not recommended for production — a network partition becomes a security gap.