Documentation v1.0.9

REST API

The daemon binds to 127.0.0.1:17989 only — 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 must pass sessions
GET /v1/daemon local — raw daemon.log content
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?, skill?, work_dir?, system_prompt?, exclude_tools?, persist?, chat?}; content is required. 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.

chat persist session_id Result
false (default) false (default) empty Creates temp-<uuid>, removed after 30 min without changes
false true empty Creates http-<uuid>, retained
true any empty Creates chat-<uuid>, retained
any any provided Uses the supplied session_id (chat and persist are ignored)
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

/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 session's reasoning setting.

Models

Method Path Description
GET /v1/models List registered models (OpenAI {data:[...]} shape, auto included)
GET /v1/models/*id Read one registered model
POST DELETE /v1/models · /v1/models/*name local — add / remove a model
GET POST /v1/model local — model routing in one object: dispatcher, summary, image, stt, tts, plus image_options, image_providers and audio_providers on read. The fields hold two different kinds of value. dispatcher, summary, stt and tts name a registered model (prefix@model) — stt and tts are drawn from GET /v1/model/audio, not from the session model registry. image names a provider endpoint (openai, codex, grok, grok-oauth, gemini) because each provider's image model is fixed inside go-llm-router; image_options lists only the providers that currently hold credentials, while image_providers and audio_providers are the full catalogs (audio_providers is openai, gemini). POST is a partial update — a field left out (or null) is untouched, "" clears it, and off is an alias of "" for image. An unregistered model, an unknown provider, or a provider with no credentials is rejected and nothing is written
GET /v1/model/audio localstt_options and tts_options: the speech-to-text and text-to-speech models reachable with the credentials stored right now, fetched live from OpenAI and Gemini and returned as provider@model. These are the only values POST /v1/model accepts for stt / tts
GET POST /v1/model/priority local — fallback order of the registered models. GET returns models (names in order), tiers (the model_tag map, model → tier) and tier_options ({tier, detail} rows, "" first for no tier). POST {models} moves the listed names to the front in that order and keeps the rest after them; an unknown name returns 400
POST /v1/model/tier local{model, tier}. Sets one registered model's tier: S, A, B, C, or pass (never picked by auto routing or subagents, last in fallback); "" clears it. An unregistered model or unknown tier returns 400

Sessions

Method Path Description
GET /v1/sessions List sessions and status
GET /v1/usage local — 24 h / 7 d / 28 d total token usage across sessions
POST /v1/session local — create a session; {prefix} defaults to cli-
GET POST DELETE /v1/session/:id local — one session's full state in one call: id, self_id, name, rule, state, model, reasoning, levels, count. POST is a partial update — self_id / name / rule / model / reasoning are all optional and a field left out (or null) is untouched; model: "" resets to auto, reasoning must be one of levels. A duplicate self_id returns 409. DELETE removes the session directory, history, state, and vectors. GET also takes ?chat=1 to append the raw action log under chat and ?usage=1 to append per-model token usage under usage; both are off by default because the log can be large
POST /v1/session/:id/event local — publish an event into a session's stream
POST /v1/session/:id/memory local — one memory operation, picked by action: summary rebuilds the rolling summary and returns count; compact drops older messages and returns removed; reset clears the conversation and returns removed, and requires modesummary keeps the rolling summary, all wipes it too
POST /v1/session/:id/cancel/:task_hash Cancel one running task. Returns {ok, cancelled:true} when that task hash is running in this process; otherwise (or for current) it records a canceled event on the session stream and returns {ok, cancelled:false, stale:true}
POST /v1/session/:id/confirm/:confirm_hash Resolve an outstanding tool confirmation: {approve, remember?, allow_turn?, abort?, reason?, password?}. 410 when the confirmation was already resolved or expired. Approving a confirmation that lists restricted paths must come from loopback (403 otherwise) and passes the OS password check with password (401 on failure); the check is skipped while the sudo ticket is still cached, which the confirm event reports as password_cached

Pending and completed tasks

Method Path Description
GET /v1/session/:id/task List resumable pending (ask_user / confirm) tasks. Tasks whose run is still live are excluded — a run refreshes action:<session_id>:<task_hash> in ToriiDB every 55 s with a 60 s TTL (v0.35.3), so a task left behind by a closed window or a killed process reappears here within a minute
GET /v1/session/:id/task/:task_hash/questions Get a pending task's questions
POST /v1/session/:id/task/:task_hash/resume Answer a pending task and resume
DELETE /v1/session/:id/task/:task_hash Discard a pending task without answering
GET /v1/session/:id/task/history local — completed tasks of this session, newest first: {task_hash, end_at, objective, model, reasoning} per row. ?keyword= filters on the objective and the recorded action text
GET /v1/session/:id/task/:task_hash/history local — the full action record of one completed task, returned as a JSON string under content. 404 when that hash has no record

Channels

Method Path Description
GET /v1/channel local — every channel read in one object: telegram and discord each carry {enabled, username, has_token}, and admin carries {channel, authorized, chats:[{value,type,id,name}]}. chats comes from the .telegram / .discord auth files (tg first, then dc) and each value can be posted back as-is; authorized says whether the current relay target is still on that list (a hand-typed ID reads false)
POST /v1/channel/telegram · /v1/channel/discord local{action:"enable"|"disable", token?}. Enable stores the token and flips the config flag only; the GetMe verification the TUI performs is intentionally skipped, since the daemon's config-file watcher already reconnects the bot and fills in its username
GET /v1/channel/:channel/chats local — chats that finished verification for telegram / discord. Only meaningful while the bot runs, so ask for it after the channel reports enabled
DELETE /v1/channel/:channel/chat local{id}. Drops one chat from that auth file; the chat has to verify again before the bot answers it. 404 when the id is not on the list
POST /v1/channel/admin local{value:"tg@<chatID>"|"dc@<channelID>"|""}. Sets where new-chat verification codes are relayed; an empty string clears it. value is required (omitting it returns 400 so an empty body cannot silently clear the setting). Only the format is validated — an ID that is not authorized makes the relay log a warning and keep the code log-only

Files & credentials

Method Path Description
GET PUT /v1/file local — read / write a file
GET /v1/file/open local — open a file or URL with the OS default handler
GET /v1/file/locate local — find candidate paths for a bare file name (name, plus optional dir=1, child, size, mtime filters)
GET /v1/workdir local — resolve and validate a work directory (?path=), returning the absolute path
GET DELETE /v1/key local — check / delete a single credential in the keychain
GET POST /v1/keys local — list / set credentials

Providers

Method Path Description
GET /v1/providers local — list providers and their available auth methods. Each row also carries logged_in, true only for the three OAuth providers (codex, copilot, grok-oauth) that currently hold a token
GET /v1/providers/quota local — remaining quota for codex, grok-oauth, copilot, ollama-cloud (kind:"percent") and remaining credit for openrouter, deepseek (kind:"balance"), returned under quota keyed by provider and fetched in parallel with a 10 s ceiling. Renamed from /v1/providers/usage in v0.35.3. Successful reads are cached in ToriiDB for 3 minutes and come back flagged cached:true; ?refresh=1 drops the cache and re-reads, and saving a key or finishing an OAuth login drops that provider's entry on its own. Providers without a credential come back with error instead of value and are never cached
POST /v1/provider/:provider/key local — set an API key
GET /v1/provider/:provider/oauth local — SSE device-code OAuth flow. An existing token is left in place while the login runs, so an abandoned or failed re-login no longer logs the provider out
DELETE /v1/provider/:provider/oauth local — clear a stored provider login (codex, copilot, grok-oauth). The token keys belong to the OAuth libraries, so this goes through their own ClearToken rather than DELETE /v1/key
GET /v1/provider/:provider/models local — list models available to this provider. For a custom compatible provider instance the model list is probed live from its endpoint (502 when the probe fails)

MCP

Method Path Description
GET POST /v1/mcp local — list / add MCP servers. The GET also returns oauth: {name: bool} for the HTTP servers, saying which ones already hold a token
POST /v1/mcp/remove local — remove an MCP server
GET /v1/mcp/status local — connection status per server
POST /v1/mcp/reconnect local — reconnect all MCP clients and re-register tools
GET /v1/mcp/oauth?name=X local — SSE OAuth login for one HTTP MCP server, mirroring the provider flow: emits {"url":...} for the browser, then {"done":true,"ok":...} (plus reconnect_error when the post-login reconnect fails). Times out after 10 minutes, or when the client disconnects
POST /v1/mcp/oauth/callback local{name, url}. Hands the redirect URL back when the browser cannot reach the daemon's loopback listener on localhost:17988; the code is parsed out of the URL's query. 400 if no login is waiting for that server
POST /v1/mcp/oauth/client local{name, client_id, client_secret?, redirect_uri?}. Stores a pre-registered OAuth client for servers that reject dynamic registration; redirect_uri defaults to http://localhost:17988/callback and must match the provider console exactly. Clears any existing token first
DELETE /v1/mcp/oauth local{name}. Clears both the stored token and the client registration for that server

Rules, notes & skills

Method Path Description
GET /v1/rules local — list session-prompt rules stored as .md files under prompts/
GET /v1/rule/*name local — read one rule
POST PATCH DELETE /v1/rule local — create / update (with optional rename) / delete a rule
GET /v1/notes local — list operator notes under notes (name, size, updated_at); records live in the SQLite note table, not on disk
GET /v1/note/*name local — read one note (name, content, updated_at)
POST PATCH DELETE /v1/note local — create / update (with optional rename) / delete a note. The name defaults to the first line when omitted, and is capped at 32 runes
GET /v1/skills local — list installed skills
GET /v1/skill/*name local — read one installed skill: name, description, path, source, content, deletable, plus files — every UTF-8 file under the skill's scripts/ references/ assets/ directories as {path, content}, sorted by path, dotfiles skipped and anything over 256 KiB omitted (v1.0.2)
DELETE /v1/skill local — remove one installed skill

The /v1/knowledge* endpoints were renamed to /v1/note* in v0.35.4; the store moved from ToriiDB to SQLite in v0.35.3. Existing notes are migrated in at daemon startup.

Automation

Cron entries and one-off tasks are one surface. The separate /v1/cron* and /v1/task* endpoints were replaced by /v1/schedule in v0.33.6, with type=cron|task selecting between them.

Method Path Description
GET /v1/schedule local — list cron entries and one-off tasks as one schedules array, each tagged type=cron|task; ?type= narrows to one
GET /v1/schedule/*skill local — read a scheduler skill: name plus body, which since v1.0.2 is the raw SKILL.md including its frontmatter rather than the parsed-off body. files carries the skill's scripts/ references/ assets/ contents on the same terms as GET /v1/skill/*name. The description field was removed
POST PATCH /v1/schedule local — create / update a scheduler skill from name / content and rebind its whole entry set to type=cron|task; switching type drops the entries the skill held under the other one. description was removed in v1.0.2. A content that already opens with its own --- frontmatter is written verbatim; otherwise the server composes a minimal ---\nname: <name>\n--- header
DELETE /v1/schedule local — delete a skill's entries (type narrows to one, omitted removes both); trashes the skill when nothing else binds it
POST /v1/schedule/run local — fire a schedule now (202 Accepted)

Allowlists

Method Path Description
GET POST /v1/allowlist local — both allowlists in one object, skill and tool. GET reads ?scope=global|project (with ?work_dir= required for project) for the skill block and ?prefix= to narrow the tool block. POST takes {skill: {name, scope?, work_dir?}} to toggle one skill and/or {tool: {prefix, entries}} to replace just that prefix's auto-approve entries (the same call the TUI's /mcp → permission makes), so unrelated rules survive; every entry must start with prefix, and prefix* collapses the rest. A block left out is untouched

Configuration

Method Path Description
GET POST /v1/config/startup local — launch-on-login. GET returns enabled (the recorded setting) and installed (whether the launchd agent or systemd user unit exists). POST {enable} (required) writes or removes the launchd agent (macOS) or systemd user unit (Linux) and returns {ok, enabled, installed, detail}; it never starts or stops the running daemon, and takes effect at the next login
GET POST /v1/config/system local — reply language. GET returns reply_lang (auto by default) and languages ({code, label} options, auto first). POST {reply_lang} (required) canonicalizes the code, rejects values over 64 bytes or containing a line break, and writes reply_lang to config.json
GET POST /v1/config/output_dir local — output directory. GET returns output_dir (the raw setting) and resolved (the directory in use; ~/Downloads when unset and that folder exists). POST {output_dir} (required) expands ~, creates the directory, and writes it to config.json; "" restores the default

Any other :target returns 404.

Inspection

Method Path Description
GET /v1/torii/error local — read the tool-error memory store. keyword alone decides the mode: without it the store is listed (?tool= narrows that listing to one tool, ?limit= defaults to 50); with it the records are searched (?limit= defaults to 16). Before v1.0.1 a bare ?tool= fell through to the search path and came back empty
PATCH /v1/torii/error local{id, action}, both required. Replaces the recorded action of one error-memory record and returns it under record; 404 when the id does not exist

The /v1/toriidb HTTP gate was removed in v0.35.3. Since ToriiDB v0.6.2 every process talks to the store over ToriiDB's own socket, so tool cache, chat vectors, error memory and pending liveness are shared without an HTTP hop.

中文