REST API
Authentication
Section titled “Authentication”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) |
/health response
Section titled “/health response”{ "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.
Agent management endpoints
Section titled “Agent management endpoints”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
Section titled “Error responses”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.)