Skip to content

REST API

Every route below needs a bearer token — Authorization: Bearer <token> — except GET /health, which is always open, and GET /metrics, which is open by default (server.metrics_auth: true requires a token). The Gateway mints two tokens on first boot into its identity directory’s device-auth.json, printed to the console at that first boot:

Token Grants Held by
admin everything — agent writes, /reload, and every read route the operator; a Gateway MCP client
invoke /rpc agent runs and every GET route the VectorStep service (executors.gateway.token)

A missing or unrecognised token gets 401; a recognised token that lacks the needed scope gets 403 — see Error responses below for the exact shape. POST /agents/validate needs admin despite writing nothing: it’s an authoring tool, and leaving it open would let an unauthenticated caller probe which models and MCP servers a deployment has configured through its error messages.

Method Path Scope needed
GET /health none
GET /metrics none, unless server.metrics_auth: true — then invoke or admin
GET /agents, /agents/{name}, /agents/{name}/soul, /agents/{name}/agent, /providers, /mcp/tools, /mcp/servers, /tool-policy invoke or admin
POST /agents, /agents/validate, /reload admin
PUT /agents/{name} admin
DELETE /agents/{name} admin
Method Path Description
GET /health Service health — status, agent count, MCP server states, active run count
GET /agents List loaded agents (name, model, model_fallbacks, tools, version)
GET /agents/{name} Combined structured view of one agent — parsed config + soul.md + raw agent.yaml text + version
GET /agents/{name}/soul Return the soul.md content for an agent
GET /agents/{name}/agent Return the agent.yaml content for an agent
POST /agents Create a new agent from raw agent.yaml/soul.md text — validates, writes, reloads
PUT /agents/{name} Update an existing agent (either or both files) — validates, writes, reloads
DELETE /agents/{name} Delete an agent — returns its prior agent.yaml/soul.md content for audit
POST /agents/validate Dry-run validation of a candidate agent — no write
GET /providers Configured providers + their model-string prefix (no API keys, no live model list)
POST /reload Reload all agent configs from disk
GET /mcp/tools List all tools across all MCP servers
GET /mcp/servers List MCP server status (pid, tool count)
GET /tool-policy Read-only view of the active tool_policy rules (reasons included) — no write endpoint, policy changes require a config edit + restart
GET /metrics Prometheus metrics (open by default — see Authentication above)
{
"status": "ok",
"version": "0.5.0",
"agents": 3,
"active_runs": 1,
"max_concurrent_runs": 10,
"mcp_servers": {
"grafana": {"running": true, "restart_count": 0},
"atlassian": {"running": true, "restart_count": 1}
}
}

status is "ok" when all configured MCP servers are running, "degraded" if any are down. A gateway with no MCP servers configured always returns "ok". No authentication is required — suitable for Kubernetes liveness/readiness probes.

POST /agents, PUT /agents/{name}, and DELETE /agents/{name} are the write path behind the Gateway MCP’s create_agent/update_agent/delete_agent tools — an agent.yaml/soul.md pair is validated (schema and that model/model_fallbacks map to a configured provider and tools: map to configured mcp_servers), atomically written, and the live registry reloaded, all before the request returns. A candidate that fails validation never touches disk — the gateway uses an atomic validated-write path for this.

POST /agents request body:

{
"name": "sre-triage",
"agent_yaml": "name: sre-triage\nmodel: anthropic/claude-sonnet-4-6\ntools: [grafana]\n",
"soul_md": "You are an SRE triage agent...",
"overwrite": false
}

Success response (200):

{
"agent": {"name": "sre-triage", "agent_yaml": "...", "soul_md": "..."},
"committed": false,
"note": "Files written and reloaded. This server never runs git commit."
}

~/.vectorstep/agents/ is an ordinary host directory, personal to the deployment — committed is always false since this server never touches git at all.

PUT /agents/{name} accepts agent_yaml and/or soul_md — omit one to leave that file untouched. The YAML’s own name: field must always match the name used to create it (in the POST body) or the URL {name} (for PUT) — a rename is a delete + create, not an update.

Error responses carry an explicit type so a caller never has to infer it from status code + message wording:

// 401 — missing or unrecognised bearer token, any route
{"detail": {"type": "unauthorized", "message": "Bearer token required"}}
{"detail": {"type": "unauthorized", "message": "Invalid token"}}
// 403 — a recognised token that lacks the scope the route needs (e.g. the
// invoke token on an admin-only route)
{"detail": {"type": "forbidden", "message": "Token 'invoke' lacks scope 'admin'"}}
// 400 — e.g. tools: references an unconfigured MCP server, model maps to no known
// provider, or name doesn't match [a-zA-Z0-9][a-zA-Z0-9_-]{0,63} (it becomes the
// agent's directory name)
{"detail": {"type": "validation", "message": "...", "errors": [{"agent": "...", "field": "tools", "value": "...", "message": "...", "severity": "error"}]}}
// 404 — PUT/DELETE on an agent that doesn't exist
{"detail": {"type": "not_found", "message": "Agent 'x' not found"}}
// 409 — POST on an existing name without overwrite: true
{"detail": {"type": "collision", "message": "Agent 'x' already exists"}}

POST /agents/validate (body: {"agent_yaml": "...", "soul_md": "..."}, soul_md optional) runs the same checks with no write — returns {"valid": bool, "errors": [...]}. This is the safe iterate loop before calling POST/PUT /agents.

GET /providers returns provider names, whether each has credentials configured, and the model-string prefix to use (e.g. "openrouter/") — never API keys, and no live per-provider model enumeration:

{"providers": [
{"name": "anthropic", "configured": true, "prefix": null},
{"name": "openrouter", "configured": false, "prefix": "openrouter/"}
]}

(prefix: null for Anthropic — a bare model name with no prefix routes there by default, per Model Routing.)