Veto/docs

Quick Start

Set up Veto in 5 minutes, or let your AI coding agent do it with one prompt.

There are two ways to get started:

PathFor whomTime
Manual setupDevelopers integrating by hand~5 minutes
AI agent integrationAI coding agents (Claude Code, Cursor, OpenCode, Codex, etc.)~1 minute

Manual setup

Prerequisites

  • Node.js 18+ or Python 3.9+
  • An existing project with AI tool calls you want to guard
  • No API key or account needed — Veto works locally out of the box

1. Install the SDK

npm install veto-sdk
pip install veto

2. Wrap tools in one line

Use protect() as the default entrypoint:

import { protect } from 'veto-sdk';

const safeTools = await protect(tools);
from veto import protect

safe_tools = await protect(tools)

For advanced control (custom config paths, explicit cloud/self-hosted settings, lifecycle ownership), use Veto.init() directly.

3. Initialize configuration

npx veto init

This creates a veto/ directory:

veto/
├── veto.config.yaml
└── rules/
    └── defaults.yaml

The generated config defaults to local mode — all validation runs in-process using your YAML rules. No API key, no account, no network calls.

4. Write your first rule

Edit veto/rules/defaults.yaml. Here's a practical rule that blocks dangerous file operations and large transfers:

veto/rules/defaults.yaml
rules:
  - id: block-sensitive-paths
    name: Block access to sensitive paths
    action: block
    severity: critical
    tools:
      - read_file
      - write_file
      - delete_file
    conditions:
      - field: arguments.path
        operator: matches
        value: "(\\.env|/etc/passwd|credentials|secrets)"

  - id: limit-transfers
    name: Block large transfers
    action: block
    severity: critical
    tools:
      - transfer_funds
    conditions:
      - field: arguments.amount
        operator: greater_than
        value: 10000

5. Add to your existing code

Pick your framework — every example below is complete and runnable.

import OpenAI from 'openai';
import { protect } from 'veto-sdk';

const openai = new OpenAI();
const tools = await protect([
  {
    type: 'function',
    function: {
      name: 'transfer_funds',
      description: 'Transfer money to an account',
      parameters: {
        type: 'object',
        properties: {
          amount: { type: 'number' },
          to: { type: 'string' },
        },
      },
    },
  },
]);

const response = await openai.chat.completions.create({
  model: 'gpt-5.2',
  tools,
  messages: [{ role: 'user', content: 'Transfer $500 to Alice' }],
});
import Anthropic from '@anthropic-ai/sdk';
import { protect } from 'veto-sdk';

const anthropic = new Anthropic();
const tools = await protect([
  {
    name: 'transfer_funds',
    description: 'Transfer money to an account',
    input_schema: {
      type: 'object',
      properties: {
        amount: { type: 'number' },
        to: { type: 'string' },
      },
    },
  },
]);

const response = await anthropic.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  tools,
  messages: [{ role: 'user', content: 'Transfer $500 to Alice' }],
});
import { Veto } from 'veto-sdk';
import { createVetoMiddleware } from 'veto-sdk/integrations/vercel-ai';
import { generateText, tool, wrapLanguageModel } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const veto = await Veto.init();
const middleware = createVetoMiddleware(veto);

const model = wrapLanguageModel({
  model: openai('gpt-5.2'),
  middleware,
});

const result = await generateText({
  model,
  tools: {
    transfer_funds: tool({
      description: 'Transfer money to an account',
      parameters: z.object({
        amount: z.number(),
        to: z.string(),
      }),
      execute: async ({ amount, to }) => {
        return { success: true, amount, to };
      },
    }),
  },
  prompt: 'Transfer $500 to Alice',
});
import { Veto } from 'veto-sdk';
import { createVetoLangChainMiddleware } from 'veto-sdk/integrations/langchain';
import { createAgent } from '@langchain/core/agents';
import { DynamicTool } from '@langchain/core/tools';

const veto = await Veto.init();
const middleware = createVetoLangChainMiddleware(veto);

const tools = [
  new DynamicTool({
    name: 'transfer_funds',
    description: 'Transfer money to an account',
    func: async (input) => JSON.stringify({ success: true, ...JSON.parse(input) }),
  }),
];

const agent = createAgent({
  tools,
  middleware: [middleware],
});

See LangChain integration for ToolNode and callback patterns.

import asyncio
from openai import OpenAI
from veto import protect

async def main():
    client = OpenAI()
    tools = await protect([
        {
            "type": "function",
            "function": {
                "name": "transfer_funds",
                "description": "Transfer money to an account",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "amount": {"type": "number"},
                        "to": {"type": "string"},
                    },
                },
            },
        },
    ])

    response = client.chat.completions.create(
        model="gpt-5.2",
        tools=tools,
        messages=[{"role": "user", "content": "Transfer $500 to Alice"}],
    )

asyncio.run(main())
import { Veto } from 'veto-sdk';

const veto = await Veto.init();

// MCP tools use inputSchema — Veto auto-detects the format
const tools = veto.wrap([
  {
    name: 'read_file',
    description: 'Read a file from the filesystem',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string' },
      },
      required: ['path'],
    },
  },
]);

See MCP integration for full server setup.

6. What a denied call looks like

When the agent tries to violate a rule, Veto throws a ToolCallDeniedError:

import { ToolCallDeniedError } from 'veto-sdk';

try {
  // Agent tries transfer_funds({ amount: 50000, to: "offshore-account" })
  // This triggers the "limit-transfers" rule
  await wrappedTool.handler({ amount: 50000, to: 'offshore-account' });
} catch (error) {
  if (error instanceof ToolCallDeniedError) {
    error.toolName;          // "transfer_funds"
    error.reason;            // "Amount 50000 exceeds limit of 10000"
    error.callId;            // "tc_abc123"
    error.validationResult;  // { decision: "deny", reason: "...", metadata: {...} }
  }
}

The agent receives the denial as a tool error and can adjust its behavior — feed the error back to the model so it retries with different arguments, throw to the caller, or degrade gracefully. See the error handling guide for framework-specific patterns.

7. Go to production

Everything above works locally — no account needed. When your agent goes to production, you'll want visibility into what it's doing: which tools it calls, which calls get denied, and the ability to require human approval for sensitive operations.

Add an API key and the SDK switches to cloud mode automatically:

const veto = await Veto.init({ apiKey: 'veto_...' });
// Or set VETO_API_KEY environment variable — the SDK auto-detects

Get your API key at runveto.com (free tier available). Your existing YAML rules keep working — cloud mode adds capabilities on top:

CapabilityLocalCloud
YAML rule validation (~1-5ms)YesYes (cached locally)
Real-time decision dashboardYes
Human-in-the-loop approvalsYes
LLM-based semantic policiesYes
Decision analytics and audit logYes
Policy packs (@veto/coding-agent, @veto/financial, etc.)Yes

The local rules you wrote in step 4 stay exactly the same. Cloud mode doesn't replace them — it extends what's possible.

Need to self-host? The server is available at ghcr.io/vulnzap/veto-server:latest — see SDK Modes.


AI agent integration

If you're using an AI coding agent (Claude Code, Cursor, OpenCode, Codex, etc.), paste this prompt to have it integrate Veto into your codebase:

## Task: Integrate Veto SDK into this codebase

Veto is the authorization layer for AI agents — it intercepts every tool call and validates
it against rules before execution. The agent never knows the guardrail exists.

### Instructions

1. Read the full documentation: https://docs.runveto.com/llms.txt
2. Install the SDK:
   - TypeScript: `npm install veto-sdk`
   - Python: `pip install veto`
3. Initialize: `npx veto init` (creates `veto/` directory with config and rules)
4. Find where tools are defined in this codebase and wrap them with `protect(tools)`
5. Create sensible default rules in `veto/rules/defaults.yaml` based on the tools in use

### Key integration pattern

```typescript
import { protect } from 'veto-sdk';
const wrappedTools = await protect(existingTools); // drop-in replacement
```

### Reference docs

- Quick start: https://docs.runveto.com/docs/getting-started/quick-start
- YAML rule format: https://docs.runveto.com/docs/rules/yaml-format
- Framework-specific guides: https://docs.runveto.com/docs/integrations/openai-sdk
  (also: anthropic-sdk, vercel-ai, langchain, langgraph, mcp, browser-use,
   openai-agents, crewai, pydanticai, playwright, google-gemini)

### Optional: enable cloud mode

If you have a Veto API key, pass it to init for dashboard + approvals + LLM policies:
```typescript
const veto = await Veto.init({ apiKey: process.env.VETO_API_KEY });
```

What the agent will do:

  1. Read the Veto docs at docs.runveto.com/llms.txt
  2. Install the SDK in your project
  3. Run npx veto init to scaffold the config directory
  4. Find your existing tool definitions and wrap them with protect()
  5. Generate default rules based on the tools it finds
  6. Optionally enable cloud mode if you provide an API key

Common rules to start with

Copy these into veto/rules/defaults.yaml as a starting point:

veto/rules/defaults.yaml
rules:
  # Block large financial transfers
  - id: limit-transfers
    name: Block large transfers
    action: block
    severity: critical
    tools:
      - transfer_funds
      - send_payment
      - create_invoice
    conditions:
      - field: arguments.amount
        operator: greater_than
        value: 10000

  # Block dangerous URLs in browser tools
  - id: block-dangerous-urls
    name: Block suspicious URLs
    action: block
    severity: high
    tools:
      - navigate
      - goto
      - open_url
    conditions:
      - field: arguments.url
        operator: matches
        value: "(data:|javascript:|file://|localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0)"

  # Require approval for destructive actions
  - id: approve-destructive
    name: Require approval for destructive operations
    action: require_approval
    severity: high
    tools:
      - delete_file
      - drop_table
      - remove_user
      - delete_repository

  # Rate-limit tool calls per session (omit tools to apply to all)
  - id: rate-limit-calls
    name: Limit total tool calls per session
    action: block
    severity: medium
    conditions:
      - field: session.totalCalls
        operator: greater_than
        value: 100

See YAML Rule Format for the full syntax and Policy Packs for pre-built rule sets.

Next steps

Where to goWhat you'll learn
YAML Rule FormatFull rule syntax — actions, conditions, condition groups, severity
Constraints ReferenceEvery constraint type — ranges, regex, enums, arrays
Error HandlingWhat to do when tools are denied — retry, degrade, inform
Policy PacksPre-built rule sets you can extend
SDK ModesLocal, cloud, and self-hosted mode explained
TypeScript SDKFull API reference
Python SDKFull API reference