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-sdkRegister 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:
chatandemptyresponses do not require verification.previewandactionresponses require verification.- Charter includes a
requestIdon 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 }
]
}
]
}
}
}
