REST API¶
GenSwarms exposes a pure JSON REST API served by Phoenix (no HTML/frontend is included). The same server also hosts a WebSocket endpoint for real-time streaming — see websocket.md.
All routes are defined in lib/genswarms_web/router.ex and implemented by the controllers in lib/genswarms_web/controllers/. This page documents every route, derived directly from those sources.
Base URL and conventions¶
- Base URL:
http://localhost:4000(the port is set by thePORTenv var, default4000). - The API pipeline accepts
application/jsononly. Send request bodies as JSON and setContent-Type: application/json. - Authentication: when
GENSWARMS_API_TOKENis set, every request must sendAuthorization: Bearer <token>; when unset, only loopback callers are accepted. The CLI attaches the token automatically. See Security. - Config-scoped token:
GENSWARMS_CONFIG_API_TOKENis a deliberately narrow grant that authorizes ONLYPATCH /api/swarms/:name/objects/:object/configandGET /api/swarms/:name/overlay— never the rest of the API. Hand this one to config tooling (the dashboard configurator) so a host can enable hot config edits without exposing the full control plane (create/delete swarms, add agents, route messages). The full token also works on the config routes; the config token works nowhere else. With neither token set, the loopback-only rule applies everywhere. - CORS is restricted to an allowlist (
GENSWARMS_CORS_ORIGINS, default local dev origins) via Corsica — see Security. - Successful responses return a JSON object. Errors return a JSON object with an
error(string) orerrors/validfield and an appropriate HTTP status (400,404,500). - Most endpoints work for both in-process swarms and daemon swarms; the controller falls back to the SQLite registry and backend CLIs (Docker / Apple
container) when a swarm runs in a separate OS process.
API info¶
| Method | Path | Description |
|---|---|---|
| GET | / | API metadata: name, version, endpoint index, and a WebSocket/route summary |
The root returns a static descriptor including name, version ("1.0.0"), description, an endpoints map, a websocket section, and a documentation map summarizing the main route groups. The websocket section advertises the channel URL (/swarm), the channel topic pattern (swarm:{swarm_name}), and the client→server / server→client event lists (see websocket.md for details).
Swarm management¶
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms | List all swarms |
| POST | /api/swarms | Create a swarm from an inline config or a config path |
| GET | /api/swarms/:name | Get detailed swarm status |
| DELETE | /api/swarms/:name | Stop a swarm (?purge=true to delete all data) |
| POST | /api/swarms/:name/pause | Pause (freeze) all Docker containers for the swarm |
| POST | /api/swarms/:name/resume | Resume paused Docker containers for the swarm |
| POST | /api/swarms/:name/restart | Restart the swarm (?delete=true for a clean slate) |
| POST | /api/swarms/:name/message | Route a message between two agents |
| POST | /api/swarms/clean | Remove stopped/crashed swarms (?all=true also clears all events) |
Notes:
GET /api/swarmsreturns{"swarms": [ ... ]}.POST /api/swarmsaccepts either{"config": { ... }}(an inline swarm config object) or{"config_path": "path/to/config.exs"}. On success it returns201 Createdwith{"status": "created", "swarm_name": "..."}. On a config/start error it returns400with{"error": "..."}. Missing both fields returns400with{"error": "Missing 'config' or 'config_path' parameter"}.GET /api/swarms/:nameenriches the status withtopology, per-agentbackend_type,skills_paths,container_name,container_status, per-objecthandler_module/source_file, and afile_pathsmap (config,data_dir=~/.subzeroclaw/swarms/<name>,log=.genswarms/logs/<name>.log). Returns404with{"error": "Swarm not found"}if the swarm is unknown.DELETE /api/swarms/:namereturns{"status": "stopped"|"purged", "swarm_name": "...", "config_path": ...}. With?purge=trueit also deletes swarm files and registry rows. Returns404for an unknown swarm.POST .../pauseand.../resumereturn a count:{"status": "paused", "swarm_name": "...", "containers_paused": N}/{"status": "resumed", "swarm_name": "...", "containers_resumed": N}. Pause/resume uses Dockerpause/unpause; Applecontainerdoes not expose equivalent semantics, so Apple container agents are not paused by these endpoints. A404is returned for an unknown swarm; a backend failure returns500.POST .../restartreads the saved config path from the registry;?delete=truedeletes data before restarting. Returns{"status": "restarted", "swarm_name": "...", "delete_data": bool}. Returns404if the swarm (or its config path) is unknown.POST /api/swarms/:name/messagerequires{"from": "...", "to": "...", "content": "..."}and returns{"status": "routed", "from", "to", "swarm"}. Missing fields return400with{"error": "Missing 'from', 'to', or 'content' parameter"}.POST /api/swarms/cleanreturns{"status": "cleaned", "swarms_removed": N, "events_cleared": bool}.
Agent operations¶
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms/:name/agents | List agents and their status |
| GET | /api/swarms/:name/agents/:agent | Get a single agent's status |
| POST | /api/swarms/:name/agents/:agent/task | Send a task to an agent |
| POST | /api/swarms/:name/agents/:agent/restart | Restart an agent |
| GET | /api/swarms/:name/agents/:agent/history | Get the agent's message history (?limit=, default 100) |
| GET | /api/swarms/:name/agents/:agent/logs | Get the agent's conversation logs |
| GET | /api/swarms/:name/agents/:agent/skills | Get the agent's skill contents |
| PUT | /api/swarms/:name/agents/:agent/skills/:skill | Update one of the agent's skill files |
Notes:
POST .../taskrequires{"task": "..."}and returns{"status": "sent", "agent", "task"}. For daemon swarms the task is queued in SQLite for the daemon to pick up. Missingtaskreturns400with{"error": "Missing 'task' parameter"}.GET .../agentsreturns{"agents": [ ... ]};GET .../agents/:agentreturns the status map directly, or404with{"error": "Agent not found"}.POST .../agents/:agent/restartreturns{"status": "restarted", "agent": "..."},404if the swarm is unknown, or500on failure.GET .../historyandGET .../logsreturn{"history": [...]}/{"logs": [...]}.GET .../skillsreturns{"skills": ...}. A missing agent returns404.PUT .../skills/:skillrequires{"content": "..."}and returns{"status": "updated", "skill": "..."}. A failure returns500.
Adding and removing agents at runtime uses
POST /api/swarms/:name/agentsandDELETE /api/swarms/:name/agents/:agent— see Dynamic topology and scaling below.
Messages¶
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms/:name/messages | Get the swarm's recent inter-agent message log |
Notes:
GET /api/swarms/:name/messagesreturns{"messages": [ ... ]}from the router's message log. Accepts?limit=(default 100). This is the routed-message history (who sent what to whom); for structured observability events use the Events endpoints instead.
Dynamic topology and scaling¶
These endpoints mutate a running swarm and persist the change as an overlay (see Overlay and snapshot).
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms/:name/topology | Get the swarm's topology (adjacency list) |
| PATCH | /api/swarms/:name/topology | Add and/or remove topology edges |
| POST | /api/swarms/:name/agents | Add a new agent to a running swarm |
| DELETE | /api/swarms/:name/agents/:agent | Remove an agent from a running swarm |
| POST | /api/swarms/:name/agents/:base/scale | Scale an agent group to a target count |
Notes:
GET .../topologyreturns{"topology": [{"from": ..., "targets": [...]}, ...]}or404for an unknown swarm.PATCH .../topologyaccepts{"add": [...], "remove": [...]}. Each edge may be["from", "to"]or{"from": "...", "to": "..."}; both endpoints of an edge must be strings, and any edge that doesn't parse is silently ignored. Returns{"status": "ok", "added": N, "removed": M}(the counts reflect the parsed edges actually applied). A mutation error returns400.POST .../agentsaccepts an agent spec (name,backend,skills,model,endpoint,presets,config) plus optionalconnections(outgoing targets) andincoming(sources).backendmay be a string (e.g."local","apple_container","mock") or an object:{"type": "docker", "image": "coder"},{"type": "apple_container", "image": "szc-agent-code:latest"},{"type": "apple_container", "image": "szc-agent-code:latest", "opts": { "memory_limit": "2g" }},{"type": "ssh", "host": "user@host"},{"type": "bwrap", "opts": { ... }}, or{"type": "mock"}. Known backend keys inconfig(workspace,network,env,volumes,cmd, resource limits, etc.) are normalized before start; unknown config keys remain domain data. Apple container rejects"network": "isolated"and fails closed instead of running with open network. Returns201 Createdwith{"status": "added", "name": "..."}, or400with{"error": "..."}on failure.DELETE .../agents/:agentreturns{"status": "removed", "name": "..."}or404with{"error": "..."}.POST .../agents/:base/scalerequires an integer{"count": N}(count >= 0) and returns{"status": "ok", "result": {"added": [...], "removed": [...], "failed": [{"name", "reason"}]}}(theadded/removed/failed[].namevalues are strings). A missing/non-integer/negativecountreturns400with{"error": "Missing or invalid 'count'"}; a scaling error returns400with{"error": "..."}.
Objects¶
Objects are the non-agentic components of a swarm. See objects.md.
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms/:name/objects | List objects with their lifecycle state |
| POST | /api/swarms/:name/objects | Add an object to a running swarm |
| GET | /api/swarms/:name/objects/:object | Get an object's live read-only state |
| PATCH | /api/swarms/:name/objects/:object/config | Update a running object's config (schema-gated) |
| DELETE | /api/swarms/:name/objects/:object | Remove an object from a running swarm |
Notes:
GET .../objectsreturns{"objects": [{"name", "state", "handler"}, ...]}, wherenameis a string andhandleris the inspected handler module (ornull).POST .../objectsaccepts an object spec (name,handlermodule name,backend,config) plus optionalconnections/incoming. Returns201 Createdwith{"status": "added", "name": "..."}, or400with{"error": "..."}on failure.GET .../objects/:objectreturns{"object": "...", "state": <handler domain state>}. The framework imposes no schema on the state. An unknown object returns404with{"error": "Object not found"}.PATCH .../objects/:object/configrequires{"config": { ... }}— a partial, string-keyed patch. It is gated by the package'sconfig_schema(theswarm-object.jsonnext to the handler's source, gsp design §14.2.1), fail-closed: a handler without a schema returns422 {"error": "no_config_schema"}; any key that is notx-mutable: truereturns422withimmutable_keys; host-escape backend keys (subzeroclaw_path,extra_*) are always rejected. On success the object restarts with the merged config (topology edges preserved; a rejectedinit/1rolls back to the old config), the patch persists as an:update_configoverlay event (replayed on boot), and the response is{"status": "updated", "object": "...", "keys": [...]}.DELETE .../objects/:objectreturns{"status": "removed", "name": "..."}or404.
Overlay and snapshot¶
A swarm's effective config is its seed config combined with an overlay of runtime mutations (added/removed agents, topology edits, scaling).
| Method | Path | Description |
|---|---|---|
| GET | /api/swarms/:name/overlay | Show the recorded overlay events |
| DELETE | /api/swarms/:name/overlay | Clear the overlay |
| POST | /api/swarms/:name/snapshot | Return the effective config (seed ⊕ overlay) as Elixir source |
Notes:
GET .../overlayreturns{"swarm": "...", "events": [{"op": ..., "payload": ...}, ...]}.DELETE .../overlayreturns{"status": "cleared", "swarm": "..."}.POST .../snapshotresponds withContent-Type: text/x-elixirand200, with the effective config rendered as a.exssource body (not JSON; produced byGenswarms.Config.ExsWriter). Returns404with a JSON{"error": "..."}if the swarm is unknown.
Events¶
Event queries read from the durable, cross-process EventStore (SQLite by default), so events from daemon swarms in other BEAM nodes are visible. See observability.md.
| Method | Path | Description |
|---|---|---|
| GET | /api/events | Query events with filters |
| GET | /api/swarms/:name/events | Query events for one swarm |
| GET | /api/swarms/:name/agents/:agent/events | Query events for one agent |
Shared query params (all optional):
| Param | Description |
|---|---|
| level | error, warning, info, or debug |
| category | backend, routing, agent, swarm, or system (see note) |
| swarm | Swarm name (implied on the scoped routes) |
| agent | Agent name (implied on the agent route) |
| event_type | A specific event type |
| minutes | Only events from the last N minutes |
| limit | Max events to return (default 100) |
Each response includes events (a list) and count. GET /api/events also echoes the normalized filters as a query map; the scoped routes echo swarm (and agent on the agent route). Every event is {id, timestamp, level, category, swarm, agent, event_type, message, metadata}; timestamps are ISO-8601 strings.
Note:
categoryis resolved withString.to_existing_atom/1, so only category names that already exist as atoms in the running system are accepted. The controller's documented set isbackend, routing, agent, swarm, system, but the broader observability taxonomy and the CLI also emit anobjectcategory — passingcategory=objectworks as long as that atom has been created (e.g. after any object event has been logged). See observability.md for the full taxonomy.
Skills¶
See skills.md.
| Method | Path | Description |
|---|---|---|
| GET | /api/skills | List available skill files (?path= overrides the search root) |
| GET | /api/skills/:name | Get a skill's content by name |
Notes:
GET /api/skillssearches the configured skills directory (the:genswarms/:skills_dirapp env, defaultpriv/skills, expanded to an absolute path) recursively and returns{"skills": [{"name", "path", "relative_path", "category"}, ...], "base_path", "count"}.categoryis the relative subdirectory (or"default"for files at the root). Pass?path=to search a different root.GET /api/skills/:namereturns{"name", "path", "content", "size"}(sizeis the byte length), or404with{"error": "Skill not found"}if the skill is not found. The.mdextension is optional in the name, and nested skills are matched by a recursive search.
Config validation¶
See configuration.md.
| Method | Path | Description |
|---|---|---|
| POST | /api/config/validate | Validate a swarm config |
POST /api/config/validate accepts one of:
{"config": { ... }}— an inline config object (must be a JSON object).{"config_path": "path/to/config.exs"}— a.exs,.json, or.yamlfile path.{"content": "...", "format": "exs"|"json"|"yaml"}— a raw config string ("yml"is accepted as an alias foryaml; an unrecognized format falls back toexs).
On success it returns {"valid": true, "config": <summary>} (the config_path / format form also echoes that input field) where the summary includes name, agent_count, object_count, topology_edges, and per-agent (name, backend, skills, model), per-object (name, handler), and topology (from/to) details. On a validation failure it returns 400 with {"valid": false, "errors": [...]}. A non-existent config_path returns 404 with {"valid": false, "errors": ["File not found: ..."]}. Sending none of the accepted fields returns 400 with {"error": "...", "usage": { ... }}.
Examples¶
List all swarms:
Create a swarm from a config file on the server:
curl -X POST http://localhost:4000/api/swarms \
-H "Content-Type: application/json" \
-d '{"config_path": "examples/research.exs"}'
Send a task to an agent:
curl -X POST http://localhost:4000/api/swarms/my-swarm/agents/researcher/task \
-H "Content-Type: application/json" \
-d '{"task": "Summarize the latest results."}'
Add an agent to a running swarm and wire it into the topology:
curl -X POST http://localhost:4000/api/swarms/my-swarm/agents \
-H "Content-Type: application/json" \
-d '{"name": "reviewer", "backend": {"type": "docker", "image": "code"}, "incoming": ["coder"]}'
Add an Apple container agent:
curl -X POST http://localhost:4000/api/swarms/my-swarm/agents \
-H "Content-Type: application/json" \
-d '{"name": "mac_coder", "backend": {"type": "apple_container", "image": "szc-agent-code:latest", "opts": {"memory_limit": "2g"}}, "incoming": ["researcher"]}'
Snapshot the effective (seed ⊕ overlay) config as runnable Elixir:
See also¶
- cli.md — the
swarmCLI, which wraps many of these endpoints. - websocket.md — real-time streaming over the WebSocket channel.
- programmatic.md — driving swarms directly from Elixir.
- configuration.md — the swarm config DSL used by create/validate.
- observability.md — the event categories and levels surfaced by the Events endpoints.