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, /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) |
/health response
Section titled “/health response”{ "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.
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.
Skill management endpoints
Section titled “Skill management endpoints”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
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.)