HTTP API
🔵 The server exposes an HTTP control plane plus a chat endpoint that streams Server-Sent Events (SSE). The list of endpoints is printed at startup in the log.
8.1 Endpoints
Pages and static assets (public)
| Endpoint | Description |
|---|---|
GET / |
markdown site (landing) |
GET /site/ · GET /site/:page |
same site under a subpath |
GET /sessions · /budgets · /teams · /users · /cron · /workflows · /dashboard |
admin UI |
GET /agents/:name |
single agent page |
GET /widget · /widget.js · /dist/* |
widget and its assets |
GET /demo · /demo/overlay |
widget demo / overlay on top of live sites |
GET /distrib/* |
distribution archives (linux/windows zip + sha256, android apk) |
GET /proxy?url=… · /proxy/content?url=… |
HTTP proxy for the frontend |
GET /login · POST /login · POST /logout |
web login for admin UI |
GET /login/oidc/:provider · GET /login/oidc/:provider/callback |
OIDC login for admin UI (Google/Yandex/Tinkoff) |
GET /v1/login/methods |
list of enabled OIDC providers |
GET /v1/login/widget/:provider · GET /v1/login/widget/:provider/callback |
OIDC login of the end user in the widget (popup) |
POST /v1/login/widget/password |
end-user login via auth.users (issues x-user-token) |
GET /v1/connectors/callback |
OAuth connector callback (Google Drive, etc.) |
GET /v1/allowed-senders/:template · POST /v1/allowed-senders/:template |
dynamic sender allowlist (list · add) 🔒 |
DELETE /v1/allowed-senders/:template/:identity |
remove a sender from the allowlist 🔒 |
Public API
| Endpoint | Description |
|---|---|
GET /health |
healthcheck |
GET /v1/models |
available models |
GET /v1/templates |
agent templates (welcome, tools; tools_declared/loaded/failed and hooks_* — what actually loaded) |
POST /v1/chat/completions |
chat with an agent (SSE, custom format) |
POST /v1/continuations |
next page of a lazily paginated table |
POST /v1/uploads |
upload attachments (file/screenshot/photo) into the session scratch |
POST /v1/input-response |
response to an agent's attachment request (request_attachment) |
POST /v1/approval-response |
end-user response to an inline approval (widget) |
POST /v1/sessions/:id |
close the session (public — the widget sends sendBeacon) |
POST /v1/inbound/:template |
one-shot agent turn from an external system (JSON, optionally bearer) |
POST /v1/user-ticket |
issue an HMAC-signed identity token |
POST /v1/feedback |
like/dislike on a response |
POST /v1/workflows/:name/webhook |
webhook workflow trigger (protected by the workflow token) |
/ext/** (custom) |
http_route / static_route — user-defined REST endpoints and static assets added globally (config/routes/) or by agents (route_files:); auth public/bearer/token_env/user per route (see §21) |
Admin API 🔒
| Endpoint | Description |
|---|---|
GET /v1/agents · POST /v1/agents |
list agents · create agent |
GET /v1/agents/:name |
snapshot of a single agent |
DELETE /v1/agents/:pid |
kill an agent and its descendants |
POST /v1/agents/:pid/signal |
steering: pause / resume / interrupt / terminate (body {"signal":"…"}) 🔒 |
POST /v1/agents/:pid/reset |
lift supervision quarantine (unpause + delete dead-letters) 🔒 |
GET /v1/dead-letters?limit=N |
quarantined agent inputs (dead-letter) |
POST /v1/agents/:name/bench/run |
run a template benchmark |
GET /v1/agents/:name/bench/runs · /bench/runs/:id |
benchmark history and detail |
GET /v1/sessions |
list sessions (?user_id= — sessions of a single user + user_stats) |
GET /v1/sessions/:id · DELETE /v1/sessions/:id |
session detail · close |
GET /v1/budgets |
budget groups and processes |
GET /v1/machines · POST /v1/machines |
inventory of managed machines · create/update (module remote) 🔒 |
GET /v1/machines/:slug · PUT /v1/machines/:slug · DELETE /v1/machines/:slug |
machine · update (slug from the path) · delete 🔒 |
GET /v1/kernels · POST /v1/kernels |
registry of remote Agent OS cores · create/update (module kernels) 🔒 |
GET /v1/kernels/:slug · PUT /v1/kernels/:slug · DELETE /v1/kernels/:slug |
core · update · delete 🔒 |
GET /v1/kernels/:slug/health |
HTTP health probe of a remote core 🔒 |
GET /v1/cron · DELETE /v1/cron/:name |
cron jobs and history · delete |
GET /v1/workflows · POST /v1/workflows/:name/run |
workflow · run |
GET /v1/workflows/runs/:id · /runs/:id/events |
workflow run · its events |
GET /v1/dashboard |
system dashboard (load, tokens, series) |
GET /v1/feedback/stats |
ratings statistics |
GET /v1/users |
channel → user mappings (with alias/operator note) |
PATCH /v1/users/:id |
set/clear {alias, note} of the canonical user |
POST /v1/users/link · POST /v1/users/:id/unlink |
merge/split identities |
GET /v1/memory/:scope |
inspect the long-term memory of a scope |
GET /v1/hooks/metrics |
hook metrics: calls/panics/timeouts/duration by (hook, point) |
GET /v1/circuit-breakers |
state of provider breakers (status, failures, opened) + config |
POST /v1/circuit-breakers/:name/reset |
force-close an open breaker |
GET /v1/approvals |
queue of pending approvals (operator inbox) |
POST /v1/approvals/:request_id |
approve/reject {approved, comment} |
GET/POST /v1/approvals/roles |
logical approver roles · create/update |
DELETE /v1/approvals/roles/:name |
delete a role |
POST/DELETE /v1/approvals/roles/:name/members… |
add/remove a role member |
GET /v1/system/tree |
platform introspection; navigation by chunks: ?view=summary|modules|types|graph, ?module=, ?type=, ?registry= (no parameters — the whole tree) |
GET /v1/system/modules · /registries · /types · /routes · /services |
introspection slices |
POST /v1/platform/modules/:id/enable · /disable |
enable/disable a module (applied after restart) |
POST /v1/platform/restart |
restart the server to apply the module set |
GET /v1/builder/agents |
list of config agents (name, presence of agent.yaml, number of files, protected) |
GET /v1/builder/agents/:name |
bundle metadata: agent.yaml + list of files {path,size} (without contents) |
GET/PUT/PATCH/DELETE /v1/builder/agents/:name/file?path= |
single file: read · write (raw body) · replace line {old,new,replace_all} · delete |
PUT /v1/builder/agents/:name |
create/partially update a bundle (agent_yaml? and/or files?) |
DELETE /v1/builder/agents/:name |
delete an agent directory |
POST /v1/builder/agents/:name/yaml?path= |
YAML edit of file {op,key,value}, op = set|append|remove |
POST /v1/builder/validate |
validate a bundle without writing (including checking each tool_files/hook_files file for the presence of kind) |
GET /v1/tools · GET /v1/tools/:name · POST /v1/tools/:name/call · POST /v1/tools/run |
tool registry: list · schema · run by name · run inline YAML |
POST /v1/agents/:name/tools/:tool/call |
run a tool in the environment of agent :name (its scope/state, session if alive), body {args} |
GET /v1/state/:scope/:id |
scoped-state keys :scope (agent|user|session|team) for :id |
GET/PUT/DELETE /v1/state/:scope/:id/:key |
read · write {value} · delete a single state key |
GET/POST /v1/teams · GET/DELETE /v1/teams/:id |
commands: list (?user_id= — user commands + roles) · create · detail (participants+grants) · delete (cascade with state) |
POST/DELETE /v1/teams/:id/members… · /grants… |
team participants and grants (admin) |
POST/DELETE /v1/my/teams/:id/… |
team owner self-service (only within own grants) |
GET /v1/docs |
documentation table of contents as JSON (languages, sections/pages {id,title}) |
GET /v1/docs/page?id=&lang= |
documentation page as markdown {id,title,lang,markdown} |
GET /v1/bench/runs · /v1/bench/catalog |
runs across all templates · templates with configured scenarios |
🔒 = requires Authorization: Bearer <токен> when admin-auth is configured
(see §8.3).
Pagination
List endpoints (/v1/sessions, /v1/agents, /v1/templates,
/v1/budgets) accept ?limit=N&offset=M. limit — page size (clamped
to 1–1000, default 100), offset — how many rows to skip (default 0).
Responses include total (and /v1/sessions — also history_total). /v1/sessions
adds history_truncated: true when there are more past sessions than the current page.
8.2 Chat completions
POST /v1/chat/completions streams Server-Sent Events (SSE). The path and request
shape are similar to OpenAI, but the response format is its own — it is not compatible with
the OpenAI SDK (no choices[].delta, no data: [DONE]).
curl http://127.0.0.1:3000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "ngu-agent",
"messages": [{"role": "user", "content": "Как поступить в НГУ?"}]
}'
Request fields:
| Field | Required | Meaning |
|---|---|---|
model |
yes | template name (ngu-agent, drom-agent, …) or built-in assistant |
messages |
yes | list of {role, content}; roles user and system enter the context |
session |
no | session token — one session = one isolated agent. Omit — legacy mode (shared template agent) |
attachments |
no | list of [{name, mime, size}] files already uploaded via POST /v1/uploads into the session scratch — the server will inject a message with their description |
stream |
no | accepted for compatibility, ignored — always streaming |
temperature |
no | accepted, ignored |
max_tokens |
no | accepted, ignored |
The X-User-Id header sets user_id for per-user budgets and memory scope.
When auth.web_user_secret_env is configured, the server requires an HMAC-signed
X-User-Token (from POST /v1/user-ticket); see §8.8.
Response (SSE events)
The body is a stream of named SSE events; done is the only one that is always present.
event |
Payload data |
|---|---|
token |
{"content": "<токен>", "final": bool} |
tool_call |
{"name": "<инструмент>", "body": "<рендер вызова>"} |
tool_progress |
{"name": "<инструмент>", "body": "<прогресс>"} |
tool_result |
{"name": "<инструмент>", "body": "<рендер результата>"} |
result_blocks |
`{"name": "<инструмент>", "blocks": [ {type: "text" |
input_request |
{"request_id": "<id>", "kind": "file"|"screenshot"|"photo", "description": "..."} — the agent requested an attachment; respond via POST /v1/input-response |
budget |
{"tokens": n, "prompt": n, "completion": n, "cached": n, "total": n, "cost": "$x.xxxx"} |
done |
{} — stream finished |
result_blocks carries the visual layer of the tool (charts, tables, text,
files); the table block may include continuation: {id} — POST /v1/continuations
with this id will return the next page of blocks, and the file block carries name, mime and
data (base64) for download. See common fields.
Errors (unknown model, session error, budget exhaustion, rate-limit)
are reported as token events inside 200, not as an HTTP error code.
8.2.1 Attachments (uploads / input-response)
Uploading files from the user (widget, Android) into the session scratch directory —
POST /v1/uploads (multipart/form-data):
model— template name (required);session_id— session token (optional, otherwise generated);file— one or more files (partsfile).
The response is {"session_id": "...", "files": [{"name", "mime", "size"}, …]}.
Files are written to <scratch>/<session>/, then the agent reads them by name
(file_tool temp, shell_tool {{temp_dir}}, vision_tool). The template's
attachments.enabled/max_size is taken into account; if attachments are disabled — 400.
An agent attachment request (tool request_attachment) arrives as an SSE event
input_request; the widget shows a prompt, the user attaches a file,
it is uploaded via /v1/uploads, after which the widget resolves the request:
curl http://127.0.0.1:3000/v1/input-response \
-H 'Content-Type: application/json' \
-d '{"request_id": "<id>", "files": [{"name": "photo.jpg", "mime": "image/jpeg", "size": 12345}]}'
An empty files = the user declined. Unknown/expired request_id
— 404; default wait is 300 sec (the tool's timeout_secs field).
8.2.2 Attention and push
When the agent calls notify_tool (or a push-sync triggers), the server
publishes an attention event to all connected clients of the user.
GET /v1/attention/stream — a long-lived SSE stream of attention events
(x-user-id / x-user-token, like chat). The client (Android foreground service,
web widget) keeps the connection open and renders the notification itself; this is the live path.
event: attention
data: {"title":"...","body":"...","level":"alert","require_ack":false,"session_id":"...","template":"...","channels":[]}
level levels: silent | notify | alert | alarm | call. alarm/call
(or require_ack: true) mean "repeat until acknowledged" — Android
shows a full-screen alarm/ring, the widget — a banner with sound.
| Method | Path | Meaning |
|---|---|---|
GET |
/v1/attention/stream |
SSE attention stream for the user |
POST |
/v1/push/register |
{kind, endpoint} — register a device push endpoint (UnifiedPush/ntfy) for wakeup |
POST |
/v1/push/unregister |
{kind, endpoint} — remove the endpoint |
If there are no live subscribers at the moment of publication, the server sends a wakeup to
the registered endpoints (without Google — via UnifiedPush/ntfy), and the killed
app wakes up and shows the notification. Details and an example — config/agents/notify-demo/README.md.
8.3 Auth
🔒 endpoints accept either Authorization: Bearer <токен> (for API clients),
or the agentos_session cookie (for the browser after web login).
- Bearer tokens — from
auth.admin_tokens(theимя → токенmap inconfig/server.yaml) orauth.admin_token_env(theимя=токенlist). The token name is written to the access log for auditing. - Web login — from
auth.users(логин → пароль).POST /loginverifies the credentials and sets theHttpOnlycookie (12 h); the login is written to the access log.POST /logoutclears the cookie. Browser UIs (/sessions,/budgets) redirect to/loginon 401. - OIDC login —
GET /login/oidc/:providerstarts Authorization Code + PKCE flow through an external provider (Google/Yandex/Tinkoff, configauth.oidc); the callback exchanges the code, pulls userinfo, and matches the identity against grantsauth.oidc.grants(subject / email / email_domain). On success — the same session cookie as for password login. No grant — login denied.
If auth is configured but no credential resolves — the admin API returns 401 for every request (fail-closed). Without tokens and users, the admin API is open (dev mode).
# API-клиент (bearer)
curl -H 'Authorization: Bearer <token>' http://127.0.0.1:3000/v1/sessions
# Web-логин (браузерный поток)
curl -c cookies.txt -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<password>"}' \
http://127.0.0.1:3000/login
curl -b cookies.txt http://127.0.0.1:3000/v1/sessions
8.4 Embedding the widget
<script src="http://127.0.0.1:3000/widget.js"></script>
The widget is served from /widget, communicates with /v1/chat/completions, and closes
sessions via POST /v1/sessions/:id (sendBeacon). A full breakdown of the widget
(configuration, model selection, demo) — in §19.
8.5 Proxy
/proxy and /proxy/content download URLs for the frontend. Restrictions:
- only
http/https, ports 80/443; - hosts only from
proxy.allow_hosts(when the list is non-empty); - private, loopback, link-local, and reserved ranges (including
169.254.169.254) are always blocked, on every redirect.
8.6 Healthcheck
curl http://127.0.0.1:3000/health
8.7 HTTPS / TLS
Set AGENT_OS_TLS_CERT and AGENT_OS_TLS_KEY (paths to PEM) — the server will bring up
HTTPS. Otherwise — plain HTTP on AGENT_OS_BIND (default 127.0.0.1:3000).
8.8 Incoming webhook and identity
Incoming webhook
POST /v1/inbound/:template — an external system (CRM, ERP, monitoring, form
backend, …) starts a single agent turn and synchronously reads the response — without SSE, without
an array of messages.
curl -X POST http://127.0.0.1:3000/v1/inbound/ngu-agent \
-H 'Content-Type: application/json' \
-d '{"message":"какие общежития у НГУ?","session_id":"crm-123"}'
Request:
| Field | Required | Meaning |
|---|---|---|
message |
yes | user message |
session_id |
no | stable id for reusing context; omitted = new session |
user_id |
no | end-user identity (Origin::Webhook); omitted = anonymous |
Response: {"answer": "...", "status": "success", "session_id": "...", "cost_usd": 0.0, "prompt_tokens": n, "completion_tokens": n, "data": null}.
Auth — bearer token when inbound.token_env is set (empty = open, dev).
End-user identity tokens
When auth.web_user_secret_env points to an env with an HMAC secret, the widget receives
a signed token and sends it as X-User-Token instead of the self-declared
X-User-Id:
curl -X POST http://127.0.0.1:3000/v1/user-ticket \
-H 'Content-Type: application/json' -d '{"user_id":"cw-browser-id"}'
# → {"user_id":"cw-browser-id","token":"cw-browser-id.<hmac>"}
Without a secret, the server trusts X-User-Id directly (dev mode).
Canonical user identities
GET /v1/users lists mappings of (origin, external_id) → canonical_id;
the operator can merge two channel ids (e.g. web-id and Telegram-id) so that
they share memory, budget, and state:
curl -X POST http://127.0.0.1:3000/v1/users/link \
-H 'Authorization: Bearer <token>' \
-d '{"survivor":"cw-browser-id","merged":"tg:123"}'
The merge migrates the scoped state store, long-term memory, and in-memory budget buckets;
subsequent sessions of both channels resolve to survivor.
8.9 Feedback
The widget sends answer ratings (like / dislike / cleared) to POST /v1/feedback
(public), and statistics — to GET /v1/feedback/stats (🔒).
A rating closes the quality loop: the memory hook on a like/dislike raises/lowers
importance of the facts recalled in that turn (§6.6). The request body includes
value, user_text, session.