REST API index (hal0 docs)

hal0-api serves an OpenAI-compatible /v1/* inference surface (see
OpenAI-compatible API reference) plus an /api/*
management surface that backs the dashboard and the hal0-admin MCP server. This page
indexes every /api/* router group mounted in src/hal0/api/__init__.py — for full
request/response schemas for a given route, read the OpenAPI spec served live at
/api/openapi.json (interactive docs at /api/docs).

Auth tiers

Every route resolves to one of five classes in src/hal0/security/exposure.py
(hal0.security.exposure.classify), evaluated first-match-wins against an explicit
rule table:

  • OPEN — no auth ever. A deliberately tiny, enumerated allowlist (the /v1/models
    probe, the Prometheus scrape, the two liveness checks, the dashboard bootstrap-urls
    endpoint, the login/status/logout routes themselves, the static SPA shell — same
    HTML/JS bundle for every visitor, no server data — and the Hermes
    dashboard-plugin static asset proxy at
    /dashboard-plugins/{plugin_name}/{file_path}).
  • BOOTSTRAP — open only until an admin key is configured (/api/install/*).
  • CLIENT — the /v1/* inference/writer surface plus a short list of genuinely
    read-only introspection GETs.
  • BRIDGE — the agent bridge’s own surfaces (ADR-0043): GET /api/bridge/me and the
    /mcp/agents mount, open to a bridge client key (hal0bk_…) or the admin key and never
    to the dev-open bypass.
  • ADMIN — everything else, including every mutating route and every route that can
    return secrets or config. Unclassified paths fall back to ADMIN (deny-by-default).

These classes are fully enforced once [security].require_auth is on. While it’s off,
the surface is trusted-LAN-open with one exception (since v1.3.0): with an admin key
configured and the API bound past loopback, ADMIN-class requests from a non-loopback
peer still need an admin credential (_lan_admin_gate in src/hal0/api/auth.py; see
Authentication). The table below states
each group’s class as enforced by exposure.py, not as a claim about the current
default posture.

Slot tune API

PUT /api/slots/{name}/config accepts {"tune":{"vision":"off"}}.
Only supplied keys change. A null key removes one override; {"tune":null}
removes all. Slot creation applies the same type/device and thinking guards.

Slot snapshots (GET /api/slots, GET /api/slots/{name}) expose
tune_effective: ctx_size, enable_thinking, reasoning_effort,
reasoning_budget, vision, speculation, sources,
reasoning_effort_unavailable and tune_unavailable. So does
GET /api/slots/{name}/resolved for a llama-server slot; the answer for a halogen
slot, like that of every other non-llama runtime, has argv: null and no
tune_effective. Sources are profile, slot or profile-flags;
reasoning_effort_unavailable is a string or null. tune_unavailable maps each
tune value the slot’s engine cannot run to the reason, and is {} on a
llama-server slot; on a halogen slot it is
{"speculation": "ngram: this engine has no n-gram drafter (auto or off)"}.
This is an additive read contract. Model feasibility accepts a slot name per model
row to resolve its profile plus tune for the candidate; explicit ctx wins.

Every GET /api/slots/{name}/resolved answer also carries footprint: null for a
slot that is not on the halogen runtime and for a halogen slot with no model bound
or no figure, otherwise {"resident_mb", "advisory_cache_mb", "host_free_mb", "verdict"}. resident_mb is the memory the engine’s layout holds once loaded (the
checkpoint, KV pool, per-slot state, working memory, prompt cache and vision tower,
never the lookup table); advisory_cache_mb is the page cache the lookup table
wants, about 10 GiB, which is advised and never part of resident_mb;
host_free_mb is the host’s free GTT pool, null when the host does not expose it.
verdict is the advisory fit of the first against the third: fits, tight
(within 10% of what is free), exceeds, exceeds_total (more than the whole pool)
or unknown (no host reading), and loaded when the slot already holds the memory.
It never blocks a load.

Router groups

Prefix Auth Purpose
/api/auth Mixed login/status/logout are OPEN (you can’t require a cookie to obtain one); require (toggle enforcement) and rotate (mint a new admin/client key) are ADMIN; exposure (serialize this whole rule table) is ADMIN.
/api/secrets ADMIN Operator-managed secrets store. See Secrets for the routes and the server-side validation — dedicated section, not just a table row.
/api/oauth Mixed OAuth passthrough for Hermes skills (see Run agents): GET /api/oauth/providers, POST /api/oauth/{provider_id}/start (returns authorize_url), GET /api/oauth/{provider_id}/status, DELETE /api/oauth/{provider_id} (best-effort revoke, then delete the token), POST /api/oauth/{provider_id}/client-secret, and GET /api/oauth/{provider_id}/callback, the provider’s redirect target. The callback is OPEN (it carries no hal0 credential, only the provider’s single-use state nonce, which the route validates); every other route is ADMIN. Tokens and client secrets persist to /etc/hal0/api.env and are never returned.
/api/settings ADMIN Root config read/write/reload, JSON schema, apply-plan registry, schema-driven field metadata (fields), and a dry-run ChangeSet preview (preview) sharing the same diff the write path applies.
/api/settings/models/store ADMIN Model store path get/set/migrate (part of the /api/settings prefix).
/api/settings/proxmox ADMIN Proxmox integration config (host/token) + live cluster status. See Proxmox integration — no dedicated exposure rule; it inherits ADMIN from the generic /api/settings prefix.
/api/slots CLIENT (list) / ADMIN (else) Slot lifecycle — list, create, delete, restart, model swap, resolved-argv provenance by layer, metrics, capacity. Slot views carry ctx_effective, ctx_source and, for a slot file that still carries tuning keys, conversion_blocked. A slot write that carries a tuning key is refused with slot.tuning_key_denied; rebind a model with PUT /api/slots/{name}/config and {"model": {"default": ...}}. See Slot lifecycle.
/api/models CLIENT (GET) / ADMIN (mutations) Model registry — list, pull, delete, inspect, scan, catalogue, update checks, and speculative-decoding drafters (PUT /api/models/{id} takes {"draft": path|null}; DELETE /api/models/{id}/draft unpairs and deletes the file; POST /api/models/inspect-draft fit-checks a drafter against a target before download; the pull body takes draft_filename and draft_repo; POST /api/models/inspect also returns drafts and companion_repos). The registry holds no image models: a pull of an image catalog id, and a create, update or add-from-path whose path is in ComfyUI’s models folder, answer 422 model.image_asset. GET /api/models/health is registered by hardware.router, not models.router — it’s mounted ahead of /api/models so the literal path wins over the {model_id} catch-all. GET /api/models also takes installed, type, capability, device, q, fields, limit and offset to filter, project and page the list (the reply gains total); with none of them it returns the full list. See Model roster & benchmarks.
/api/runner-images CLIENT (list/detail) / ADMIN (sync/pull) Runner Image catalogue — sync from images.json, pull-job orchestration, tag removal and restart-affected-slots for GHCR-hosted runner images.
/api/ports CLIENT (GET) / ADMIN (else) Port allocation view.
/api/hardware CLIENT (GET) / ADMIN (/probe) Hardware probe read + on-demand re-probe.
/api/stats/hardware CLIENT Live hardware probe/stats, and the memory block the dashboard’s pool, RAM and per-owner memory figures are rendered from. See the Memory budget API reference and the Hardware matrix.
/api/stats (/slots, /requests, /throughput/history, /power) CLIENT (GET) / ADMIN (else) Dispatcher rollups, throughput history, power stats.
/api/status CLIENT Version/build summary. Includes build_sha, the short git SHA of the tree the running process started from — null on a release (non-git) install.
/api/health, /api/health/system OPEN Shallow + deep liveness probes hit by the installer/systemd watchdog before any credential exists.
/api/doctor ADMIN GET /api/doctor — the same report card hal0 doctor verify --json prints, aggregated in-process from health/urls/system/memory/services, over HTTP. Classified ADMIN (not CLIENT) because it re-surfaces detail from ADMIN-only subsystems.
/api/metrics CLIENT JSON metrics snapshot.
/api/metrics/prometheus OPEN Prometheus scrape target.
/api/features CLIENT (GET) Feature-flag surface. GET /api/features is the only route health.py registers on this prefix — no mutating route is mounted, so exposure.py’s ADMIN-mutations rule is currently latent, not backing a real endpoint. The CLIENT rule is an exact match on /api/features only, so a hypothetical GET /api/features/<sub> would fall through to the prefix rule and resolve ADMIN.
/api/system-info, /api/system-stats CLIENT Host system info / fleet metrics over the SQLite tables (OBS-1).
/api/capabilities ADMIN Capability-slot configuration (embed/rerank/stt/tts/vision child slots). GET takes section (backends, catalogs, selections; comma list) and summary=true to shrink the payload; with neither it returns everything.
/api/backends CLIENT (GET) / ADMIN (else) Backend introspection (NPU/GPU-Vulkan/GPU-ROCm/CPU status) + NPU load/unload.
/api/npu CLIENT (GET) / ADMIN (else) NPU trio swap-status for the “swap incoming” dashboard banner.
/api/providers ADMIN Local provider entries (providers.toml). See Providers, profiles & devices.
/api/upstreams ADMIN Remote/cloud upstream entries (upstreams.toml), including credential write and connectivity test. Mounted on the same router as /api/providers. Each entry also carries managed_by (null, or agent:hermes for the per-profile Hermes upstreams hermes and hermes.<profile>), pinned and owned_models. An agent-managed upstream is read-only here: PATCH, DELETE and the credential write return 400 upstream.protected; change it through the agent.
/api/stacks ADMIN Stack bundles — list, create, apply, import/export, snapshot.
/api/profiles CLIENT (GET) / ADMIN (mutations) Profiles, the tune a slot launches a model with (flags, ctx_size, enable_thinking, vision, speculation, runner) — list (each entry carries used_by, the slot names), create, update, import/export (envelope schema 2). speculation is auto, off or ngram; a body that still sends the retired mtp is 422 profiles.field_renamed.
/api/benchmarks ADMIN Benchmark queue, run status, control. See Model roster & benchmarks.
/api/chat-templates ADMIN Chat-template catalog — bundled + operator-added templates.
/api/logs ADMIN Secret-redacted log tail.
/api/mcp ADMIN MCP server lifecycle/introspection — not tool invocation. See MCP tools for the full endpoint table.
/api/comfyui ADMIN ComfyUI generation-engine status and image-gen slot control, plus the image-model catalog. GET /api/comfyui/models lists the catalog with each model’s install state (none, partial, complete), the size still to download and in total (missing_gb and approx_gb; a GB is 2^30 bytes here, as in the memory budget) and its latest install job, plus a top-level engine: {available, reason} (available is false with a reason on a host with no ROCm lane; the catalog is listed either way); POST /api/comfyui/models/{model_id}/install starts a download and returns 202 with the job (it also clears a removal DELETE recorded), and DELETE /api/comfyui/models/{model_id} removes the model’s weight files and workflow and answers {"removed": [...], "kept": [{"relpath": ..., "models": [...]}], "affected": [...]}: a weight file another complete model loads stays and is listed in kept with the models that hold it, and ?include_shared=true (default false) deletes it too, lists in affected the models that were complete and no longer are, and deletes their workflows. A delete records the removal, so a model that stays complete on shared weights is not given its workflow back at the next start. An install is refused with 404 comfyui.model_unknown, 409 comfyui.install_running, 409 comfyui.model_installed (every weight is on disk and its workflow is provisioned; a model with the weights but no workflow is accepted, downloads nothing and writes the workflow), 409 comfyui.staging_cross_device, 409 comfyui.models_dir_not_writable (a destination directory the hal0 user cannot write, or a models tree it cannot read, which GET /models and DELETE also answer; error.details.path names it) or 507 comfyui.insufficient_disk. A delete is refused with 404 comfyui.model_unknown, with 409 comfyui.install_running while that model’s install, or the install of a model sharing a weight file with it, is active, and with 409 comfyui.remove_failed when a file cannot be deleted (error.details has removed, the paths deleted before the failure, failed_path and error). GET /api/comfyui/jobs lists the latest install job of each model (state, bytes_downloaded, bytes_total), POST /api/comfyui/jobs/{model_id}/cancel stops the model’s install (model_id is the model’s id, not the job’s id; 404 comfyui.job_not_found when the model has no job), and GET /api/comfyui/files lists the weight files under the models tree with the catalog models that own each. DELETE /api/comfyui/files/{subdir}/{relpath} deletes one file no catalog model loads and answers {"deleted": "<subdir>/<relpath>"}; it is refused with 400 comfyui.file_path_invalid (subdir is not a model directory, or relpath is absolute, climbs out with .., has a hidden part or does not name a weight file: .safetensors, .ckpt, .pt, .pth, .gguf, .bin or .sft), 400 comfyui.file_symlink, 404 comfyui.file_not_found, 409 comfyui.file_catalog_owned (error.details.models names the owners) or 403 comfyui.file_permission. The model rows’ fit verdicts, GET /api/comfyui/models/{model_id}/fit, the arbiter’s phase fields, progress, and the GPU gate’s gate and gate_refusal (POST /api/comfyui/gate-refusal is the gate’s own) are in the Image readiness API reference.
/api/install BOOTSTRAP First-run wizard endpoints, including GET /api/install/curated-models. Open until an admin key is configured, then treated as ADMIN.
/api/memory ADMIN Two routers share this prefix: the [memory.graph] gate/status, and the Hindsight engine admin surface (banks, graph, recall/reflect, mental models, directives, async operations). Every DELETE under this prefix is pinned ADMIN by an explicit rule independent of the generic one (issues #1024/#1302 — a prior unauthenticated bank-delete cascade-dropped ~632 live records); bank-scoped deletes additionally require a ?confirm= echo + dry-run preview. GET /api/memory/graph/status reports the engine target as target ({kind: "slot"|"upstream", model, alias: "hal0/memory"}), the upstream ids the engine can run on as available_models ([{id, owned_by}]) and the LLM hindsight-api really loads as engine_llm ({provider, base_url, model, overridden, override_file, override_keys, source}; read from the running hindsight-api process when hal0-api can (source: "process"), else from the unit plus /etc/hal0/hindsight-llm.env as systemd would apply them at the next start ("unit+file", or "unit" when there is no readable file); overridden is true when that file sets the provider, base URL or model, and null when hal0-api can read neither the process nor the file, which the dashboard and CLI show as “cannot verify”). The status call also fills an empty upstream model cache (after a hal0-api restart) by fetching each provider’s catalog, retrying an offline provider at most once a minute, so available_models is populated on the first poll. PUT /api/memory/graph accepts extraction_model (an upstream model id, or null for the local extraction_slot); an id no configured upstream advertises is refused with 422 config.memory_graph_model_invalid and neither a slot nor a model change restarts the engine. A bank_id under /api/memory/banks/{bank_id}/… may contain dots (coding-agent::ultrareview-1.4) as well as colons; a leading dot, a dot-only segment and any .. return 400 memory.invalid_bank.
/api/board ADMIN Operator Board — a live, audited proxy to the Hermes kanban plugin: task/board CRUD, links, dispatch, and WS /api/board/events. Frozen FE↔BE contract (ui/CONTRACTS.md §4). Needs a running Hermes: 503 board.not_installed (no Hermes agent), 503 board.unreachable (dashboard not answering), 503 board.no_token (token file missing); Hermes’s own errors come back as board.upstream_error (4xx passed through, 5xx as 502).
/api/brain ADMIN POST /api/brain/chat (SSE) — the primary hal0-brain platform-steward chat surface. Drives the full admin tool surface (slot mutations, model pulls, board writes), hence ADMIN.
/api/updates ADMIN Self-update: release-manifest check, prepare/commit/apply/rollback, channel get/set, post-update slot-drift report + restart. Job state is also kept under /var/lib/hal0/update-jobs/: a job read back from there after a hal0-api restart that is still queued or running was killed with the old process and reads failed with error_code: "api_restarted" and an error that says what was interrupted (“hal0-api restarted during the repair; the live release is unchanged” for a Hermes repair, “… during the build; …” for a Hermes converge, the one atomic builder, and “hal0-api restarted before this job finished; check hal0 update status before retrying” for any other job); a finished job reads as it was.
/api/events ADMIN Dashboard dock event feed — backfill + GET /api/events/stream (SSE). See the auth-tier note above.
/api/activity ADMIN Durable audit surface (SQLite-backed) — the source of truth for the dock’s activity rows (kind=action), with /export (csv/json) and /stream (SSE). See the auth-tier note above.
/api/journal ADMIN Unified journal panel — flattens /api/events into one envelope for the dashboard journal view. See the auth-tier note above.
/api/images ADMIN Cache for PNGs written by POST /v1/images/generations. ADMIN because a cached-image URL could otherwise leak a prompt via filename.
/api/agents ADMIN Bundled-agent lifecycle (install/uninstall/list/status), personas, service restart, Hermes gateway restart and Hermes repair (see the Agents API reference), memory stats, and Hermes command approvals (GET/PUT /api/agents/hermes/command-approvals). The command-approvals PUT also refuses a request whose X-hal0-Agent header names an agent. POST /api/agents/hermes/reconcile-upstreams re-registers the Hermes profiles as upstreams on demand (it runs the profile API-key sweep first) and returns {ok: true, ...report}; 404 agent.not_installed when Hermes is not installed. hal0-api also reconciles every 120 s and after an agent install or uninstall. The agent chat proxy is gone: /api/agents/{id}/events, /submit and /session/handshake|create|resume|history return 404. GET /api/agents/{name}/overview is the state behind the Hermes and Pi cards (see the Agents API reference).
/api/agent/approvals ADMIN Gated-tool approval inbox — every method (GET list, POST approve/deny, GET /events SSE) is ADMIN; exposure.py’s rule on this prefix carries no method filter. Approve and deny also check the caller themselves at every posture: a key of any tier gets 403 auth.forbidden, and only a session cookie decides (who can decide).
/api/bridge BRIDGE (GET /me) / ADMIN (else) Agent bridge clients for remote coding agents (ADR-0043) and the runs they delegate to Hermes; every route, the bridge.* error codes, the output paging rule and which routes work while [bridge].enabled is false are in the Agent bridge API reference.
/api/config Mixed GET /api/config/urls (dashboard bootstrap hostnames) is OPEN; the rest of this prefix is ADMIN (may hold config-derived secrets).
/api/meta CLIENT (GET) / ADMIN (else) GET /api/meta/enums — the canonical device/backend/capability vocabulary from hal0.model_meta.
/api/hf CLIENT (GET) / ADMIN (else) HuggingFace Hub search proxy backing the dashboard’s “Search HF” button.
/api/services ADMIN services_health.router (GET /api/services/health, stable overview iterating hal0.services.registry.SERVICES — comfyui, hermes, hindsight, openwebui) and services_routes.router (full per-service detail + lifecycle actions + mDNS) share this prefix.
/api/dashboard/plugins ADMIN Hermes dashboard-plugin manifest (kanban plugin discovery for <AgentView>).
/api/openrouter ADMIN OpenRouter OAuth callback scaffold — only mounted when HAL0_OPENROUTER_OAUTH_ENABLED is truthy; 501 placeholder otherwise absent from the route table entirely.

POST /api/memory/add returns 503 memory.engine_unavailable when the
memory engine connection fails or connecting times out. The message asks the
caller to retry shortly; error.details.retry_after_s and Retry-After are
both 5 seconds. An error response from the engine is not reclassified as
engine unavailability.

Every prefix above is backed by at least one route confirmed directly against
src/hal0/api/routes/ and the mount list in src/hal0/api/__init__.py. This brings
documented coverage to every /api/* prefix currently mounted (previously 18 of
roughly 43). WS /v1/realtime is documented separately — see
Realtime API reference.

Secrets and Proxmox

The /api/secrets routes and their server-side validation, and the
/api/settings/proxmox routes, are documented in
REST API: secrets and Proxmox credentials.

Agent overview, restart and repair

GET /api/agents/{name}/overview (the state behind the Hermes and Pi cards),
POST /api/agents/hermes/restart, POST /api/agents/hermes/gateway/restart and
POST /api/agents/hermes/repair (a tracked job, served by
GET /api/updates/status/{id}) are documented in the
Agents API reference.