Skip to content
Beta. VectorStep is in beta.Let us know if something breaks.

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, /skills, /skills/{name}, /skills/{name}/files/{path}, /providers, /mcp/tools, /mcp/servers, /tool-policy invoke or admin
POST /agents, /agents/validate, /skills, /skills/validate, /reload admin
PUT /agents/{name}, /skills/{name} admin
DELETE /agents/{name}, /skills/{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, skills, skill_versions, version)
GET /agents/{name} Combined structured view of one agent — parsed config + soul.md + raw agent.yaml text + skill_versions + 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 /skills List loaded skills (name, description, version, files, executable_scripts)
GET /skills/{name} Combined structured view of one skill — description + raw SKILL.md text + bundled file list + version
GET /skills/{name}/files/{path} Return one bundled file’s raw content, plus binary: true/false
POST /skills Create a new skill from raw SKILL.md text + optional bundled files — validates, writes, reloads
PUT /skills/{name} Update an existing skill — validates, writes, reloads
DELETE /skills/{name} Delete a skill — returns its prior SKILL.md content for audit
POST /skills/validate Dry-run validation of a candidate skill — no write
GET /providers Configured providers + their model-string prefix (no API keys, no live model list)
POST /reload Reload all agent and skill 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.1.20",
"agents": 3,
"active_runs": 1,
"max_concurrent_runs": 10,
"mcp_servers": {
"grafana": {"running": true, "restart_count": 0},
"atlassian": {"running": true, "restart_count": 1}
}
}

version is the release tag the image was built from. A gateway run from a source checkout, with no VECTORSTEP_VERSION set, reports dev (before v0.1.21 it reported a stale hardcoded 0.5.0).

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.

POST /skills, PUT /skills/{name}, and DELETE /skills/{name} are the write path behind the Gateway MCP’s create_skill/update_skill/delete_skill tools — mirrors the agent management endpoints above, adapted for a skill’s richer (multi-file) shape. See Skills for the on-disk format and progressive disclosure model.

POST /skills request body:

{
"name": "incident-triage",
"skill_md": "---\nname: incident-triage\ndescription: Use when...\n---\n\n# Incident Triage\n...",
"files": {"reference/escalation-matrix.md": "..."},
"overwrite": false
}

Success response (200):

{
"skill": {
"name": "incident-triage", "skill_md": "...",
"files": ["reference/escalation-matrix.md"], "version": "a1b2c3d4e5f6"
},
"committed": false,
"note": "Files written and reloaded. skills/ is gitignored, so this is not a git-commit concern."
}

PUT /skills/{name} accepts skill_md and/or files — omitting files leaves the skill’s existing bundled files untouched, but providing it is a full replace, not a merge: a file that existed before but is missing from the new files map is deleted. To add one file without touching the rest, GET /skills/{name} first to see the current set, then pass the full merged map back. The SKILL.md’s own name: frontmatter field must always match the name used to create it or the URL {name} — a rename is a delete + create, not an update, same as agents.

GET /skills / GET /skills/{name} both include executable_scripts — the subset of that skill’s bundled files currently approved to run via run_skill_script (see Skills — Running a skill’s bundled scripts). This is a read-only fact derived from config.yaml’s security.skills.allowed; no endpoint here can change it.

GET /skills/{name}/files/{path} returns {"name", "path", "content", "binary"} — binary: false with the file’s text in content for an ordinary bundled file, or binary: true with content: null for a file that isn’t valid UTF-8 (e.g. an image), rather than the 500 an earlier version of this endpoint returned for that case.

GET /agents and GET /agents/{name} both include skill_versions — {skill_name: version} for every name in that agent’s skills: list, with null for a grant whose skill isn’t currently loaded on this gateway. This is the granted-skills half of version’s hash input made visible; see the :::note in Skills — Granting a skill for what changes to it mean for calibration.

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.)