REST API
Last updated
The daemon listens on the loopback addresses only —
127.0.0.1:17989 and [::1]:17989 — so LAN clients cannot reach it. The port is fixed and not configurable.The same daemon serves the web dashboard at
/. The dashboard is embedded into the binary at build time, so http://127.0.0.1:17989 is the whole surface — there is no hosted front-end. AGENVOY_PAGE_DIR swaps the embedded copy for files on disk during development (make dev).Since v1.0.1 the dashboard also runs offline.
GET /sw.js serves a service worker that precaches the embedded assets under a versioned cache; GET /vendor/* serves the third-party assets (QuickUI, Font Awesome, Material Symbols, and the nanomd / voice scripts) from ~/.config/agenvoy/vendor/, which the daemon downloads once at startup and re-downloads when the binary version changes. Nothing is fetched from a CDN at page load. In AGENVOY_PAGE_DIR mode /sw.js instead serves a teardown worker that clears its caches and unregisters itself, so a dev build is never served from a stale cache.Endpoints marked local additionally require the request to originate from
127.0.0.1/::1 (the localhostOnly() guard). They manage credentials, config files, or process lifecycle and are meant for a same-machine dashboard, not remote clients. Unmatched routes carry the same guard: an unknown /v1/ path returns a JSON 404, and any other unknown path falls back to the dashboard index.Agent execution
| Method | Path | Description |
|---|---|---|
POST | /v1/send | Run an agent request |
POST | /v1/chat/completions | Stateless OpenAI-compatible chat completion |
GET | /v1/info/version | Build version stamped at compile time ({version, dev}); dev is true for an untagged build |
GET | /v1/log | SSE stream. With no query it carries daemon slog records only (EventDaemonLog frames, source = level) — the same feed the TUI header shows. It includes new-chat verification codes, so daemon frames are attached only for loopback callers. ?sessions=a,b adds those sessions' events on the same connection; replay=0 skips the backlog, daemon=0 drops the daemon frames. A remote caller, or one passing daemon=0, must pass sessions (400 otherwise). Since v1.3.8 session events carry backend-formatted text — trace (the thinking or tool-call line, e.g. ⏵ API(Weather)), tool_display on tool calls and confirmations, and confirm_title, restricted_lines and restricted_note on confirmations — so every surface shows the same wording |
GET | /v1/daemon | local — {content}: the daemon.log lines of the last 28 days. ?from= / ?to= (yyyy-MM-dd-HH-mm, local time) narrow the window and ?keyword= keeps only lines containing it (case-insensitive); a malformed time returns 400 |
GET | /v1/system/update | local — added in v1.0.11. {version, latest, update_available}; latest is resolved by following the GitHub releases/latest/download/x redirect (502 with the running version when that lookup fails) |
POST | /v1/system/update | local — added in v1.0.11. Opens a terminal window running agen update and returns 202 {status:"opened"}; the update itself runs in that terminal, not in the daemon. macOS uses Terminal.app, WSL launches the Windows terminal, Linux tries xdg-terminal-exec, x-terminal-emulator, gnome-terminal, konsole, xfce4-terminal, kitty, alacritty, wezterm, xterm in that order. 501 when no terminal is reachable |
GET | /v1/mcp/tools | List the tools registered from connected MCP servers (mcp__*) |
POST /v1/send semantics
The body is
{content, session_id?, sse?, model?, reasoning?, skill?, work_dir?, system_prompt?, exclude_tools?, persist?}; content is required. Since v1.3.5 model and reasoning apply to this request only and never change the session default: an omitted model uses the session model, auto runs the dispatcher even when the session is pinned, and an omitted reasoning uses the session level. The web chat keeps its picks in browser cookies and sends them here. work_dir defaults to the home directory and must resolve to an existing directory (400 otherwise). A request that arrives while the session is still running is appended to that run as a steer message instead of starting a new one, and returns {session_id, steer:true} at once. Otherwise sse:true streams the run's events; without it the reply arrives when the run ends as {text, model, usage, session_id}, or {session_id, canceled:true} when it was canceled (500 with error when it failed).persist | session_id | Result |
|---|---|---|
false (default) | empty | Creates temp-<uuid>, removed after 30 min without changes |
true | empty | Creates chat-<uuid>, retained |
| any | provided | Uses the supplied session_id (persist is ignored) |
The
chat flag was removed in v1.0.11 and the http- prefix with it: a persisted HTTP session now gets the same chat- prefix the dashboard uses, so one prefix covers every persisted non-CLI session. Existing http- sessions keep working when named explicitly through session_id.- curl --fail-with-body -sS \
- -H 'Content-Type: application/json' \
- -d '{"content":"List the available tools","persist":false}' \
- http://127.0.0.1:17989/v1/send
POST /v1/chat/completions: OpenAI-compatible router endpoint
/v1/chat/completions is stateless: include prior messages in every request when continuity is needed. reasoning_effort accepts none low medium high xhigh max (plus the aliases minimal extra ultra); an omitted or unrecognized value falls back to the level the model selector picks for the request (medium when it picks none). model defaults to auto.By default a completion runs with none of Agenvoy's own tools: only the
tools the client sends are offered. With the header X-Agenvoy-Agent: 1 (or true / yes) it runs as an Agenvoy agent instead — Agenvoy's system prompt plus every tool except the TUI-only ones. Tool confirmations are skipped on this endpoint either way.Any OpenAI client can use Agenvoy as its model router: set the SDK's base URL to
http://127.0.0.1:17989/v1. The endpoint does not check the API key, so any placeholder value works. GET /v1/models lists every registered model plus auto (see Model Endpoints).model | Which model answers | Fallback |
|---|---|---|
auto (default) | The dispatcher model picks a worker model for the request's work kind (see Model Routing) | When a send fails, the next model in the priority order that is on a different provider and passes a 10-second health check takes over, for up to four rounds |
<provider>@<model> | That registered model as-is; an unregistered name fails with [Not found] model | None |
Timeouts and rate limits are retried on the same model up to three times before any fallback. Without
stream the reply is one chat.completion object, and an error that survives fallback returns 502 with type: upstream_error; with stream: true the reply is SSE chat.completion.chunk frames ending in data: [DONE]. usage reports prompt_tokens (cache reads included), completion_tokens and prompt_tokens_details.cached_tokens. When the client sends tools and the model calls one, the reply carries tool_calls with finish_reason: "tool_calls" and the client runs the tool itself. When the system prompt comes from Zed, the project root listed in it becomes the work directory; otherwise the home directory is used.LiteLLM is a proxy server that puts many providers behind one OpenAI-format API, and OpenRouter is a hosted API that routes requests across providers. Agenvoy's endpoint runs on your own machine and routes across the models you registered in Agenvoy, including the Claude Code and Codex subscription providers, choosing one by work kind.