Skip to content
/docs

Charter SDK

Use Charter from the Veto REST API to chat, compile policies, and hand policy changes to Veto verification.

Charter is a server-side authoring agent accessible via the Veto REST API. It compiles natural-language policy requests into policy drafts and routes production changes through a verification link that a human must approve before anything goes live.

Install

npm install veto-sdk

Register Tools

Register the tools your agent can call so Charter can compile policies against the real runtime surface.

import { Veto } from "veto-sdk";

const veto = await Veto.init({
  baseUrl: process.env.VETO_API_URL ?? "https://api.veto.so",
  apiKey: process.env.VETO_API_KEY!,
});

await veto.registerTools([
  {
    name: "issue_refund",
    description: "Issue a customer refund",
    parameters: [
      { name: "amount_usd", type: "number", required: true },
      { name: "ticket_age_days", type: "number", required: true },
    ],
  },
]);

MCP tools can be registered with veto.wrapMCPTools(tools, serverClient) which wraps execution and registers the tool inventory simultaneously.

Send a Charter Turn

Charter turns are REST calls to POST /v1/charter/turn. Authenticate with X-Veto-API-Key.

const response = await fetch("https://api.veto.so/v1/charter/turn", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Veto-API-Key": process.env.VETO_API_KEY!,
  },
  body: JSON.stringify({
    message: "Refunds over $200 require a support lead approval.",
    mode: "compile",
    surface: "api",
  }),
});

const turn = await response.json();

if (turn.verification.required) {
  console.log("Review before publish:", turn.verification.linkUrl);
}

Streaming

Use POST /v1/charter/stream to receive Server-Sent Events with progress updates while Charter classifies and compiles.

const res = await fetch("https://api.veto.so/v1/charter/stream", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Veto-API-Key": process.env.VETO_API_KEY!,
  },
  body: JSON.stringify({
    message: "Block prod DB writes unless an on-call lead approves.",
    mode: "compile",
    surface: "api",
  }),
});

const reader = res.body!.getReader();
const decoder = new TextDecoder();

for await (const chunk of readSSE(reader, decoder)) {
  const event = JSON.parse(chunk.data);

  if (event.type === "status" || event.type === "tool") {
    process.stdout.write(`${event.text}\n`);
  }

  if (event.type === "result" && event.data.verification.required) {
    console.log("Review before publish:", event.data.verification.linkUrl);
  }

  if (event.type === "error") {
    console.error(event.message);
  }
}

CLI Device-Token Transport

After veto cloud login, terminal agents have a CLI access token and can call the CLI-scoped Charter transport (/v1/cli/charter/turn and /v1/cli/charter/stream) using Authorization: Bearer <token>.

curl -N https://api.veto.so/v1/cli/charter/stream \
  -H 'Authorization: Bearer vcl_at_xxx' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Refunds over $200 require support lead approval.",
    "mode": "compile",
    "surface": "cli"
  }'

Conversation State

Pass recent turns when building your own CLI, MCP server, or agent bridge:

const response = await fetch("https://api.veto.so/v1/charter/turn", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Veto-API-Key": process.env.VETO_API_KEY!,
  },
  body: JSON.stringify({
    message: "Make the same rule stricter for weekends.",
    mode: "auto",
    surface: "mcp",
    sessionId: "run_01J...",
    history: [
      { role: "user", content: "Block prod DB writes unless an on-call lead approves." },
      { role: "charter", content: "Drafted prod_db_query as a Veto policy..." },
    ],
  }),
});

surface can be "api", "cli", "mcp", or "dashboard". Use it to keep logs, request IDs, and automation flows attributable.

Response Contract

type CharterTurnResponse = {
  requestId: string;
  terminalState:
    | "answered"
    | "needs_clarification"
    | "needs_verification"
    | "no_tools"
    | "failed";
  result:
    | { kind: "chat"; message: string; requestId: string }
    | { kind: "clarify"; clarification: unknown; requestId: string }
    | { kind: "preview"; preview: unknown[]; unmatched: string[]; requestId: string }
    | { kind: "action"; actions: unknown[]; message: string; requestId: string }
    | { kind: "empty"; message: string; unmatched?: string[]; requestId: string };
  verification: {
    required: boolean;
    linkUrl: string | null;
    expiresAt: string | number | null;
    preview: unknown[];
    unmatched: string[];
  };
};

Rules for production surfaces:

  • chat and empty responses do not require verification.
  • preview and action responses require verification.
  • Charter includes a requestId on every turn so clients can ignore stale responses after aborts, scope changes, or retries.

Tool Context

Charter compiles against registered tools. Use VetoAdmin to inspect and manage the registered tool inventory:

import { VetoAdmin } from "veto-sdk";

const admin = new VetoAdmin({
  baseUrl: process.env.VETO_API_URL ?? "https://api.veto.so",
  apiKey: process.env.VETO_API_KEY!,
});

const tools = await admin.listTools();
await admin.deleteTool("old_tool_name");

MCP agents can also register tools through Charter's native MCP server:

{
  "jsonrpc": "2.0",
  "id": "register-tools",
  "method": "tools/call",
  "params": {
    "name": "veto_register_tools",
    "arguments": {
      "tools": [
        {
          "name": "issue_refund",
          "description": "Issue a customer refund",
          "parameters": [
            { "name": "amount_usd", "type": "number", "required": true }
          ]
        }
      ]
    }
  }
}