vmlab-web: the REST + WebSocket API

Everything the web console does rides this API, so it can be scripted with plain HTTP. All /api/* routes sit behind the auth gate when auth is enabled: POST /api/login ({"user","password"}) returns a bearer token, GET /api/auth probes whether login is required, and POST /api/logout revokes the session. Lab and machine names appear in paths; {action} routes accept the verb in the path (e.g. up, down, start, stop). VMs and containers share one …/machines/{name}/… path family — they are the same thing to the API even though the console presents them separately. Where a machine cannot serve a request it says why: a container has no framebuffer, so screenshot.png fails with "machine web has no display", and a VM keeps no console log, so logs fails in kind. Ask …/machines/{name}/capabilities first — it reports display, console_log, reboot and the guest agent's negotiated features, which is what the console drives its affordances from.

AreaEndpoints
LabsGET/POST /api/labs (list, create — the create body takes preset: "empty" (default) or "starter"; anything else is a 400), GET /api/labs/{lab} (status), POST /api/labs/{lab}/{action} (up/down/destroy/…), POST …/reload, GET …/dns, GET …/logs, POST …/pulls/{machine}/cancel (abort an in-flight registry pull)
Machines (VMs + containers)POST …/machines/{name}/{action} (start/stop/restart/destroy), GET …/capabilities, POST …/sendkeys, GET …/screenshot.png, GET …/stats, GET …/logs, GET/POST …/clipboard, GET/POST …/files?path= (one file out of / into the guest over the agent — the POST body is the file's bytes, the GET answers with them; 8 MiB each way), GET …/snapshots, DELETE …/snapshots/{name}
Snapshots (lab-wide)POST …/snapshots (take), POST …/snapshots/{name}/restore
TemplatesGET …/templates, GET …/templates/ops, GET …/templates/{tpl}/remote, POST …/templates/{tpl}/build|stop|publish
PlaybooksGET …/playbooks, GET …/playbooks/ops, GET …/playbooks/plays (every play the lab's folders declare), POST …/playbooks/scaffold (takes the play name; the folder need not be declared yet, and names that would break the skeleton's WCL literals are rejected), POST …/machines/{name}/playbook/{action} (check/apply); config-weave packages: POST …/playbooks/pkg, POST …/playbooks/pkg/search, GET/POST …/playbooks/repos
Playbook designerPOST …/playbooks/doc/inspect and POST …/playbooks/doc/render (config-weave's DocJson pipeline: source ⇄ structural document), GET …/playbooks/catalog (installed packages' resource + parameter declarations, which drive the property panels)
Files tabGET …/files/tree, GET/PUT/DELETE …/files/file, POST …/files/mkdir, POST …/files/rename — sandboxed to the lab directory
Config & designerGET/POST …/config (raw vmlab.wcl), GET/PUT …/scripts, GET …/model + POST …/model/edit (the designer's structured model + surgical edit ops)
Catalogs & hostGET /api/catalog/templates|profiles|meta|oci, DELETE /api/catalog/templates/{arch}/{name}/{version}, POST /api/catalog/templates/{arch}/{name}/{version}/verify (the Templates page's Check), GET/POST/DELETE /api/registries + POST /api/registries/login, GET /api/host, GET /api/host/fs, GET /api/fastpath
Live streams (WS)GET /api/events (lab/daemon event feed), GET /api/labs/{lab}/machines/{name}/tty (agent terminals), GET /api/desktop/vnc/{lab}/{vm} and GET /api/labs/{lab}/templates/{arch}/{template}/vnc (desktop protocol)
Guest web pagesPOST /api/web/session mints the path-scoped vmlab_web cookie; the proxy itself serves /web/{lab}/{kind}/{machine}/{page}/… outside the /api/ gate
DocsGET /help, GET /help/{path} — this reference book, embedded
console
TOKEN=$(curl -s -X POST :7878/api/login \
  -d '{"user":"admin","password":"…"}' | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" :7878/api/labs | jq .
curl -s -X POST -H "Authorization: Bearer $TOKEN" :7878/api/labs/demo/up