docs: OpenAPI and API.md cover every route; drop removed endpoints; guard config writes
- openapi-extra/run.js describes tmux pane runs only: the removed
POST /api/run/{id}/message and stream-json run_stream/run_input_ack are gone.
- 108 operations added to OpenAPI (lanes, run, settings, reports, cc-config,
pets, ai-proxies, tool-profiles, ai-tools, snapshots, locks, ...);
openapi.yaml regenerated.
- docs/API.md: 74 routes added, 4 endpoints that no longer exist removed.
- Role is `developer`, not `member` (CLI.md, DATABASE.md, OVERVIEW.md);
`agenthub update` -> `update-check`; help lists `server install-system|tools`
and `users provision`; ARCHITECTURE.md's stale paths and names fixed.
- cc-config/codex-config writes carry sameOriginGuard (an MCP server or plugin
is code Claude runs later), with a test; `command -v` takes its name as $1.
This commit is contained in:
+235
-148
@@ -22,6 +22,10 @@ Complete REST API and WebSocket documentation for Agent Dashboard.
|
||||
- [Remote Data Sources](#remote-data-sources)
|
||||
- [Query Explorer](#query-explorer)
|
||||
- [Scheduled reports](#scheduled-reports)
|
||||
- [Webhooks](#webhooks)
|
||||
- [Web Push](#web-push)
|
||||
- [Updates](#updates)
|
||||
- [Import history](#import-history)
|
||||
- [WebSocket API](#websocket-api)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Rate Limiting](#rate-limiting)
|
||||
@@ -327,6 +331,25 @@ by one, 0.5 s as one batch.
|
||||
GET /api/homes { homes: [{ id: "me" | <username>, label, readOnly, dir? }] }
|
||||
GET /api/homes/:who/fs/list|trash|zip|download as the workspace routes above
|
||||
POST /api/homes/:who/fs/mkdir|newfile|rename|move|copy|delete|upload|trash/restore|trash/purge
|
||||
GET /api/homes/trash { items, errors, keepDays } — every Trash the caller may open (below)
|
||||
```
|
||||
|
||||
One line per route, for searching (bodies and answers as the workspace routes above; `copy` takes `fromLane` instead of `fromHome`, `trash/restore` and `trash/purge` also take `store`):
|
||||
|
||||
```
|
||||
GET /api/homes/:who/fs/download?path=
|
||||
GET /api/homes/:who/fs/zip?p=<path>&p=…
|
||||
GET /api/homes/:who/fs/trash
|
||||
POST /api/homes/:who/fs/newfile
|
||||
POST /api/homes/:who/fs/rename
|
||||
POST /api/homes/:who/fs/move
|
||||
POST /api/homes/:who/fs/copy
|
||||
POST /api/homes/:who/fs/delete
|
||||
POST /api/homes/:who/fs/trash/restore
|
||||
POST /api/homes/:who/fs/trash/purge
|
||||
POST /api/homes/:who/fs/upload?path=&conflict=
|
||||
POST /api/homes/:who/fs/upload-check
|
||||
POST /api/homes/:who/fs/upload-batch?dir=&conflict=
|
||||
```
|
||||
|
||||
The same File Explorer on a user's HOME (`server/lib/homes.js`): under S3 their
|
||||
@@ -549,6 +572,14 @@ Error `409 ENOCWD` is returned when the workspace's folder no longer exists (or
|
||||
|
||||
Error `409 ERUNLIVE` is returned if the workspace already has a live run; stop it first with the `stop` action. The same code and status come back when the workspace's tmux pane is already running an agent while a `prompt` or `resumeSessionId` was sent — that argv cannot be typed over a live agent, so the request is refused instead of answered 200 with nothing done. A start that asks for neither still adopts the existing pane silently.
|
||||
|
||||
#### Other workspace actions
|
||||
|
||||
```http
|
||||
POST /api/lanes/:id/:action
|
||||
```
|
||||
|
||||
The one control route behind `start` (above), `stop`, `message`, `clear`, `reset`, `remove` and `purge` (same-origin). Any other `:action` is `400 EBADACTION`, an unknown workspace `404 ENOLANE`. `reset`, `remove` and `purge` are destructive and need `{ "confirm": true }` (`400 ECONFIRM`) — see below.
|
||||
|
||||
#### Preflight a destructive workspace action
|
||||
|
||||
```http
|
||||
@@ -1274,6 +1305,33 @@ anything is spawned, and `args` travels as an array of strings straight into
|
||||
argv — neither is ever joined into a command string. An unknown name returns
|
||||
`400 ENOHOOK` with the allowed list; a workspace with no slot returns `409 ENOSLOT`.
|
||||
|
||||
#### Scaffold a profile, sync MCP servers, read an integration
|
||||
|
||||
```http
|
||||
POST /api/lanes/:id/profile/init
|
||||
POST /api/lanes/:id/mcp/sync
|
||||
GET /api/lanes/:id/integrations/:name
|
||||
```
|
||||
|
||||
`profile/init` (same-origin) detects a Node.js project in the workspace's OWN folder and scaffolds `.agenthub/profile/` there — the HTTP twin of `agenthub lanes profile init`. `{ force?: true }` overwrites; answers `{ scaffolded: true, written, todos }`, or `{ scaffolded: false, reason }` (200) when no Node.js project is found; `400 EPROFILEEXISTS` without `force`. `mcp/sync` (same-origin) copies the source repo's `mcpServers` from `~/.claude.json` into `<workspace>/.mcp.json` (paths relocated) and seeds Playwright profiles → `{ servers, profilesSeeded }`; `400 ENOMCPCONFIG` when the source has none. Never automatic — a session calls it. `integrations/:name` → `{ enabled }`, read from `.agenthub/profile/integrations.env`. All three `404 ENOLANE` for an unknown workspace; under S3 the two writes refuse a member's folder the way the runtime routes do.
|
||||
|
||||
#### CI status and base sync
|
||||
|
||||
```http
|
||||
GET /api/lanes/:id/ci/status?ref=<branch|sha>
|
||||
POST /api/lanes/:id/sync-base
|
||||
```
|
||||
|
||||
`ci/status` is ONE snapshot of GitHub Actions for `ref` through `gh` in the workspace's folder — not a poll loop: `{ state: "pending"|"success"|"failure", ref, checks: [{ name, conclusion, url }] }`; `400 EBADREF` without `ref`. `sync-base` (same-origin, under the workspace lock, needs the workspace profile) merges the profile's base branch INTO the workspace's branch behind a migration-number collision preflight: body `{ mode?: "check"|"merge"|"continue" (default merge), branch? }`. Every documented outcome — clean, a collision, a conflict left in place — is a 200 with `code` 0/4/5; only a malformed request, a missing profile or an out-of-order `continue` answers an `error`. It never writes the stage.
|
||||
|
||||
#### Housekeeping
|
||||
|
||||
```http
|
||||
POST /api/lanes/gc
|
||||
```
|
||||
|
||||
Same-origin, admin only (`403 EADMIN` for a developer or analyst — it is machine-wide). Reaps orphaned Playwright MCP processes under `LANES_ROOT` and caps oversized logs under `LANES_ROOT/.state/`. `{ dryRun?: true }` reports without acting. Answers `{ reaped, capped }`.
|
||||
|
||||
#### Tail a workspace's log
|
||||
|
||||
```http
|
||||
@@ -1564,6 +1622,17 @@ Both `GET /api/workflows` and `/skill-usage` also take `from=YYYY-MM-DD&to=YYYY-
|
||||
query failure.
|
||||
|
||||
|
||||
#### Workflow-tool runs
|
||||
|
||||
```http
|
||||
GET /api/workflows/runs?status=&session_id=&limit=&offset=
|
||||
GET /api/workflows/runs/:runId
|
||||
GET /api/workflows/session/:id
|
||||
```
|
||||
|
||||
`runs` lists the fleets the Claude Code **Workflow** tool launched, ingested from their on-disk journals (`server/lib/workflow-ingest.js`): `{ runs, total, counts, limit, offset }`, newest first, `phases`/`progress` parsed into arrays, `counts` per status; `status` (omit or `all` for every one) or `session_id` filters, `limit` 1–1000 (default 50). A developer sees only runs of their own sessions. `runs/:runId` → `{ workflow, agents, events }` — the run, its inner agents and their events (oldest first, at most 5000); `404 WORKFLOW_NOT_FOUND`. `session/:id` is the session drill-in of the Workflow analytics page: `{ session, tree, toolTimeline, swimLanes, events }` (the agent tree, every tool call in order, one swim lane per agent, the first 500 events); `404` for an unknown session.
|
||||
|
||||
|
||||
### Locks
|
||||
|
||||
Cross-workspace named locks (`server/lib/named-lock.js`) — the OTHER axis from a
|
||||
@@ -1772,6 +1841,16 @@ classDiagram
|
||||
|
||||
---
|
||||
|
||||
#### Create Session
|
||||
|
||||
```http
|
||||
POST /api/sessions
|
||||
```
|
||||
|
||||
Body `{ id, name?, cwd?, model?, metadata? }` (`id` required, else `400 INVALID_INPUT`). An existing id answers `200 { session, created: false }` unchanged; a new one is inserted `active` and broadcast as `session_created` → `201 { session, created: true }`. Sessions normally come from hooks; in server mode a developer or analyst is refused (`403 EADMIN`).
|
||||
|
||||
---
|
||||
|
||||
#### Update Session
|
||||
|
||||
```http
|
||||
@@ -1961,53 +2040,24 @@ curl http://localhost:4820/api/sessions/sess_abc123/stats
|
||||
|
||||
---
|
||||
|
||||
#### Get Session Agents
|
||||
#### Session transcripts
|
||||
|
||||
```http
|
||||
GET /api/sessions/:id/agents
|
||||
GET /api/sessions/:id/transcripts
|
||||
GET /api/sessions/:id/transcript?agent_id=&run_id=&limit=&after=&before=&offset=
|
||||
```
|
||||
|
||||
Returns all agents for a session.
|
||||
`transcripts` lists the session's conversation files — the main one first, then each subagent's and compaction's — as `{ transcripts: [{ id, name, type: "main"|"subagent"|"compaction", subagent_type?, db_agent_id, … }] }`; `db_agent_id` links an entry to its `agents` row (`null` when none matches). A Codex, Antigravity, Cursor, omp or OpenCode session lists its own tool's journal instead.
|
||||
|
||||
**Path Parameters:**
|
||||
`transcript` reads one of them as structured messages: no `agent_id` = the main conversation, `agent_id` (+ `run_id` for a Workflow-tool agent) = a subagent's — each one plain name, never a path. `limit` defaults to 50 (max 200); `after=<line>` returns only messages past that JSONL line (the chat's tail), `before=<line>` the page before it (history), `offset` is the legacy pager. Answers `{ messages, total, has_more, last_line, first_line }` (empty when the file is gone). The live file under the Claude projects folder is preferred, else the snapshot taken at import. `404 NOT_FOUND` for an unknown session.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `id` | string | Session ID |
|
||||
---
|
||||
|
||||
**Example Request:**
|
||||
#### Get Session Agents
|
||||
|
||||
```bash
|
||||
curl http://localhost:4820/api/sessions/sess_abc123/agents
|
||||
```
|
||||
There is no `/api/sessions/:id/agents` route: a session's agents come with `GET /api/sessions/:id` (its `agents` array) or from `GET /api/agents?session_id=<id>` (see [Agents](#agents)).
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": [
|
||||
{
|
||||
"id": "sess_abc123-main",
|
||||
"session_id": "sess_abc123",
|
||||
"name": "Main Agent - my-project",
|
||||
"type": "main",
|
||||
"subagent_type": null,
|
||||
"status": "idle",
|
||||
"current_tool": null,
|
||||
"task": null,
|
||||
"started_at": "2024-03-18T12:00:00Z",
|
||||
"ended_at": null,
|
||||
"updated_at": "2024-03-18T12:05:00Z",
|
||||
"parent_agent_id": null,
|
||||
"awaiting_input_since": "2024-03-18T12:05:00Z",
|
||||
"awaiting_reason": "stop",
|
||||
"cost": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Note on `cost`** — `/api/agents` and `/api/sessions/:id/agents` attach a `cost` (USD) to each agent: the agent's **own** cost, computed server-side from the per-agent token buckets stored in `agents.metadata.tokens` and priced at the current pricing rules (at the agent's start date, so promo/standard cutovers apply — see [Pricing](#pricing)). It is `0` for main agents (whose cost is the session total, reported by `/api/pricing/cost/:sessionId`), for compaction pseudo-agents, and for any subagent whose transcript is unavailable. This lets a subagent card show only what that subagent spent instead of the whole session's total.
|
||||
> **Note on `cost`** — `/api/agents` and `GET /api/sessions/:id` (its `agents`) attach a `cost` (USD) to each agent: the agent's **own** cost, computed server-side from the per-agent token buckets stored in `agents.metadata.tokens` and priced at the current pricing rules (at the agent's start date, so promo/standard cutovers apply — see [Pricing](#pricing)). It is `0` for main agents (whose cost is the session total, reported by `/api/pricing/cost/:sessionId`), for compaction pseudo-agents, and for any subagent whose transcript is unavailable. This lets a subagent card show only what that subagent spent instead of the whole session's total.
|
||||
|
||||
> **Note on `status` vs Waiting** — agents are persisted with one of `idle | connected | working | completed | error`. The yellow **Waiting** badge surfaced in the dashboard is a UI overlay derived from `awaiting_input_since` being non-null on a non-terminal agent (typically `idle` after a `Stop`, or `connected` right after `SessionStart`). Filter `?status=idle` on `/api/agents` and inspect `awaiting_input_since` to enumerate currently-waiting main agents.
|
||||
|
||||
@@ -2071,54 +2121,18 @@ curl http://localhost:4820/api/agents/agent_xyz789
|
||||
|
||||
---
|
||||
|
||||
#### Get Agent Tools
|
||||
#### Create or update an agent
|
||||
|
||||
```http
|
||||
GET /api/agents/:id/tools
|
||||
POST /api/agents
|
||||
PATCH /api/agents/:id
|
||||
```
|
||||
|
||||
Returns tool executions for an agent.
|
||||
`POST` body `{ id, session_id, name, type?, subagent_type?, status?, task?, parent_agent_id?, metadata? }` (`id`, `session_id`, `name` required, else `400 INVALID_INPUT`): an existing id answers `200 { agent, created: false }` (`404 ENOTFOUND` when it is another user's in server mode); a new one is inserted (`type` default `main`, `status` default `waiting`), broadcast as `agent_created` → `201 { agent, created: true }`. `PATCH` takes any of `name`, `status`, `task`, `current_tool`, `ended_at`, `metadata` and answers `{ agent }`; `404 NOT_FOUND` for an unknown id. Agents normally come from hooks.
|
||||
|
||||
**Path Parameters:**
|
||||
#### Tool Execution Flow
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `id` | string | Agent ID |
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:4820/api/agents/agent_xyz789/tools
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"id": 1,
|
||||
"agent_id": "agent_xyz789",
|
||||
"tool_name": "bash",
|
||||
"duration_ms": 1234,
|
||||
"success": 1,
|
||||
"error_message": null,
|
||||
"created_at": "2024-03-18T12:01:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"agent_id": "agent_xyz789",
|
||||
"tool_name": "view",
|
||||
"duration_ms": 45,
|
||||
"success": 1,
|
||||
"error_message": null,
|
||||
"created_at": "2024-03-18T12:02:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Tool Execution Flow:**
|
||||
Tool calls are recorded as `PreToolUse`/`PostToolUse` events, not as a separate table — read them with `GET /api/events?agent_id=<id>` or `?tool_name=<name>`.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -2129,13 +2143,13 @@ sequenceDiagram
|
||||
participant DB as Database
|
||||
|
||||
Agent->>PreHook: Tool about to execute
|
||||
PreHook->>DB: Set current_tool
|
||||
PreHook->>DB: Store PreToolUse event, set current_tool
|
||||
|
||||
Agent->>Tool: Execute (bash, view, etc.)
|
||||
Tool-->>Agent: Result
|
||||
|
||||
Agent->>PostHook: Tool completed
|
||||
PostHook->>DB: Create tool_execution record
|
||||
PostHook->>DB: Store PostToolUse event
|
||||
PostHook->>DB: Clear current_tool
|
||||
PostHook->>DB: Update token counts + cost
|
||||
```
|
||||
@@ -2154,6 +2168,16 @@ Returns `{ events, limit, offset, total }`, newest first, filtered by `event_typ
|
||||
|
||||
---
|
||||
|
||||
#### Event filter values
|
||||
|
||||
```http
|
||||
GET /api/events/facets
|
||||
```
|
||||
|
||||
`{ event_types, tool_names }` — the distinct values in the events table, sorted, for the Events page's filters (a developer's: only their own sessions' events).
|
||||
|
||||
---
|
||||
|
||||
### Data cleanup
|
||||
|
||||
#### Clean up old data
|
||||
@@ -2198,50 +2222,57 @@ POST /api/settings/uninstall
|
||||
|
||||
Settings → System's **Uninstall AgentHub** (same-origin only). Body `{ "confirm": true }` — anything else is `400 ECONFIRM`; in server mode `403 ESERVERMODE` (a shared dashboard is removed with `agenthub uninstall` on the host). Spawns `scripts/uninstall.js --yes` detached and answers `202 { ok: true, log }` at once; the worker then stops this server and removes everything `agenthub uninstall --yes` does (see [CLI.md](CLI.md)), the database included. `log` is `$TMPDIR/agenthub-uninstall-<uid>.log`, the only record left afterwards.
|
||||
|
||||
#### Clear, re-import, export and restore data
|
||||
|
||||
```http
|
||||
POST /api/settings/clear-data
|
||||
POST /api/settings/reimport
|
||||
GET /api/settings/export
|
||||
POST /api/settings/import
|
||||
POST /api/settings/reset-pricing
|
||||
```
|
||||
|
||||
`clear-data` deletes every session, agent, event, token row, workflow run, fired alert and webhook delivery in one transaction (alert rules, webhook targets, pricing, snapshots and reports survive) → `{ ok: true, cleared: <table counts before> }`. `reimport` re-reads the Claude projects folder (`importAllSessions`) → `{ ok: true, imported, skipped, errors, … }`; `500 IMPORT_FAILED`. `export` streams one JSON attachment (`agent-monitor-export-<date>.json`: `format`, `version`, `exported_at`, `sessions`, `agents`, `events`, `token_usage`, `workflows`, `dashboard_runs`, `alert_rules`, `model_pricing`), events written in pages so a large database never sits in memory. `import` (same-origin) restores such a file sent as multipart `file` (≤ 512 MB, else `413 FILE_TOO_LARGE`): non-destructive — a session already here is skipped whole — → `{ ok: true, source, format, …counts }`; `400 NO_FILE` / `BAD_UPLOAD` / `INVALID_FORMAT`. `reset-pricing` reseeds the default model prices (and intro rates), then waits up to 3 s for the automatic price sync → `{ ok: true, pricing }`; `{ scope: "claude" }` resets only the `claude-…` rows. All five are admin-only in server mode, and `export` is staff-only.
|
||||
|
||||
#### Hooks for Claude Code, Codex and Antigravity
|
||||
|
||||
```http
|
||||
POST /api/settings/reinstall-hooks
|
||||
GET /api/settings/codex-hooks
|
||||
POST /api/settings/codex-hooks
|
||||
DELETE /api/settings/codex-hooks
|
||||
GET /api/settings/antigravity-hooks
|
||||
POST /api/settings/antigravity-hooks
|
||||
DELETE /api/settings/antigravity-hooks
|
||||
```
|
||||
|
||||
`reinstall-hooks` rewrites AgentHub's hooks in Claude Code's `settings.json` → `{ ok, hooks }` (`500 HOOK_INSTALL_FAILED`). `codex-hooks` reads, installs (or reinstalls) and removes AgentHub's entries in `$CODEX_HOME/hooks.json` (`server/lib/codex-hooks.js`); every answer is the status `{ supported, path, script, readable, installed, trusted, events: { <Event>: { installed, trusted } }, error? }` — `trusted` is read from Codex's own `config.toml`, which AgentHub never writes. `antigravity-hooks` does the same for the named hook `agenthub` in `~/.gemini/config/hooks.json` (status `{ supported, path, script, readable, installed, enabled, events }`). Only on a click; the writes are same-origin; `400 EBADJSON` / `EUNSUPPORTED` / `EFOREIGN` (a forwarder script that is not AgentHub's), else `500 ESAVE`.
|
||||
|
||||
#### Privacy
|
||||
|
||||
```http
|
||||
GET /api/settings/privacy
|
||||
PUT /api/settings/privacy
|
||||
```
|
||||
|
||||
`{ redact_secrets, ignore_cwd, env_owned: { redact_secrets, ignore_cwd } }` — opt-in redaction of secrets in stored payloads, and the folders whose hooks are dropped (`server/lib/privacy.js`). `PUT` (same-origin) takes either field (`redact_secrets` boolean, `ignore_cwd` a list of absolute folders) and answers `{ ok: true, …state }`; `400 INVALID_INPUT` / `EBADPATH` (not absolute, a filesystem root, over 100 entries), `409 EENVOWNED` when the environment sets it, `500 ESAVE`.
|
||||
|
||||
#### Push ingest token
|
||||
|
||||
```http
|
||||
GET /api/settings/push-token
|
||||
POST /api/settings/push-token
|
||||
DELETE /api/settings/push-token
|
||||
```
|
||||
|
||||
The token for [`POST /api/hooks/ingest-batch`](#push-ingest-post-apihooksingest-batch). `GET` → `{ enabled, env_owned }` — never the token. `POST` (same-origin) makes a new one (the old one stops working) and returns it THIS ONCE: `{ ok: true, token, enabled, env_owned }`; `DELETE` turns push ingest off → `{ ok: true, enabled: false, env_owned }`. `409 EENVOWNED` when `AGENTHUB_PUSH_TOKEN_SHA256` comes from the environment.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Tools
|
||||
|
||||
#### List All Tools
|
||||
|
||||
```http
|
||||
GET /api/tools
|
||||
```
|
||||
|
||||
Returns all tool executions across all sessions.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `limit` | integer | 100 | Max tools to return |
|
||||
| `tool_name` | string | - | Filter by tool name |
|
||||
| `success` | boolean | - | Filter by success status |
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:4820/api/tools?limit=50&tool_name=bash
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"id": 1,
|
||||
"agent_id": "agent_xyz789",
|
||||
"tool_name": "bash",
|
||||
"duration_ms": 1234,
|
||||
"success": 1,
|
||||
"error_message": null,
|
||||
"created_at": "2024-03-18T12:01:00Z"
|
||||
}
|
||||
],
|
||||
"total": 156
|
||||
}
|
||||
```
|
||||
There is no `/api/tools` route. Per-tool counts come from `GET /api/sessions/:id/stats` (`tools_used`, `tool_call_count`) and `GET /api/events/facets`; individual calls from `GET /api/events?tool_name=<name>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -2290,6 +2321,27 @@ A ready-made Prometheus config, recording rules and three Grafana boards built o
|
||||
|
||||
---
|
||||
|
||||
#### Overview counts
|
||||
|
||||
```http
|
||||
GET /api/stats?tz_offset=&sources=&tool=
|
||||
```
|
||||
|
||||
The Dashboard's header numbers: the overview counts (sessions, agents, events…), `events_today` (in the local day of `tz_offset`, the browser's `getTimezoneOffset()`), `ws_connections`, `agents_by_status` and `sessions_by_status` (`{ <status>: count }`). `sources`/`tool` narrow it like `/api/sessions`; a malformed `tool` is `400 INVALID_INPUT`.
|
||||
|
||||
#### Health and API docs
|
||||
|
||||
```http
|
||||
GET /api/health
|
||||
GET /api/openapi.json
|
||||
GET /api/redoc
|
||||
GET /api/redoc/redoc.standalone.js
|
||||
```
|
||||
|
||||
`health` → `{ status: "ok", version, timestamp }`, exempt from every login. `openapi.json` is this API's OpenAPI 3.0 document (also browsable at `/api/docs`, Swagger UI); `redoc` renders it with ReDoc, whose script `redoc.standalone.js` is served locally (no CDN).
|
||||
|
||||
---
|
||||
|
||||
### Pricing
|
||||
|
||||
#### List Pricing Rules
|
||||
@@ -2535,41 +2587,28 @@ POST /api/alerts/rules/preview
|
||||
|
||||
### Notifications
|
||||
|
||||
#### Get Session Notifications
|
||||
Notifications are not stored per session and have no REST read. Hook `Notification` events land in the events table (`GET /api/events?session_id=<id>&event_type=Notification`), and the dashboard's alerts feed is [`GET /api/alerts`](#alerts-feed).
|
||||
#### Alert rules
|
||||
|
||||
```http
|
||||
GET /api/sessions/:id/notifications
|
||||
GET /api/alerts/rules
|
||||
POST /api/alerts/rules
|
||||
PATCH /api/alerts/rules/:id
|
||||
DELETE /api/alerts/rules/:id
|
||||
```
|
||||
|
||||
Returns notifications for a session.
|
||||
`GET` → `{ rules: [{ id, name, rule_type, config, enabled, cooldown_seconds, … }] }` (`config` parsed). `POST` body `{ name, rule_type, config, enabled?, cooldown_seconds? }`: `rule_type` is one of `event_pattern`, `inactivity`, `status_duration`, `token_threshold`, `cost_threshold`, `budget`, and `config` is validated for it (`validateRuleConfig`); `cooldown_seconds` defaults to 300 → `201 { rule }`. `PATCH` takes `name`, `config` (checked against the stored type — `rule_type` is immutable), `enabled`, `cooldown_seconds` → `{ rule }`. `DELETE` → `{ ok: true }`, its fired alerts cascading away. `400 INVALID_INPUT`, `404 NOT_FOUND`. In server mode rules are staff-only and their writes admin-only. See [Spend Budgets](#spend-budgets) for `budget` and the read-only preview.
|
||||
|
||||
**Path Parameters:**
|
||||
#### Alerts feed
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `id` | string | Session ID |
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:4820/api/sessions/sess_abc123/notifications
|
||||
```http
|
||||
GET /api/alerts?unacked=true&limit=&offset=
|
||||
POST /api/alerts/:id/ack
|
||||
POST /api/alerts/ack-all
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
`GET` → `{ alerts, total, unacked, limit, offset }`, newest first (`limit` 1–200, default 50; `unacked=true` only the unacknowledged). A developer's feed is their own sessions' alerts (one with no session — a budget — is staff-only); an analyst's rows carry `can_ack`. `:id/ack` acknowledges one (`:id` numeric) → `{ alert }`, broadcast as `alert_updated`; `404 NOT_FOUND`. `ack-all` → `{ ok: true, acknowledged }` (a developer's or analyst's: their own sessions' only).
|
||||
|
||||
```json
|
||||
{
|
||||
"notifications": [
|
||||
{
|
||||
"id": 1,
|
||||
"session_id": "sess_abc123",
|
||||
"notification_type": "backgroundTaskComplete",
|
||||
"message": "Explore agent completed",
|
||||
"created_at": "2024-03-18T12:05:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Remote Data Sources
|
||||
|
||||
@@ -2895,6 +2934,7 @@ POST /api/run/:id/permission-mode {mode} → Shift+Tab steps, each read back
|
||||
POST /api/run Start/attach run — Body: { laneId, cwd?, model?, permissionMode?, effort?, resumeSessionId?, initialPrompt?, sandbox?, approval?, resumeLast?, bypass? }
|
||||
GET /api/run/:id Run handle (returns live run state)
|
||||
DELETE /api/run/:id[?archive=1] Kill the pane → {ok}. `archive=1` (the UI's Stop) also archives the session it held → {ok, archived: <session id> | null}; without it (Sleep, the CLI) the session stays listed to resume
|
||||
DELETE /api/run/history/:id Forget one CLOSED Terminal tab (`tool = 'shell'`) from the history now → {removed}; 409 ERUNLIVE while open, 404 ENOTFOUND for any other row
|
||||
```
|
||||
|
||||
`:id` must be a workspace pane name, `agenthub-lane-<n>`; anything else answers 404 without reaching tmux, so these two routes can never touch a tmux session the dashboard did not start.
|
||||
@@ -3145,6 +3185,53 @@ POST /api/settings/compact { confirm: true } → 202 — started (or alr
|
||||
Settings → Data → **Session cleanup** (`server/lib/db-retention.js`). Automatic retention is OFF unless `days` (1–3650, `AGENTHUB_RETENTION_DAYS` in the settings file) is set; nothing is deleted by default. When set, the hourly maintenance (`DASHBOARD_DB_MAINTAIN_MS`) purges finished (completed/error/abandoned) sessions that started more than `days` ago — the same rule and the same deletion (`purgeSessionIds`: events, agents, token usage, the session) as `POST /api/settings/cleanup` with `purge_days` — 25 sessions per transaction with a yield between, at most 5000 per run. A new database is created with `auto_vacuum = INCREMENTAL`, an existing one up to 32 MB is converted by a VACUUM at startup; the same hourly run then returns free pages to the OS (`incremental_vacuum`, in 1024-page slices, at most 65536 pages per run) once more than 1024 are free. A larger, older database is converted only by **Compact**: a full VACUUM in a worker thread followed by a TRUNCATE checkpoint — it holds the write lock while it runs (tens of seconds on a GB-size file), so hooks wait meanwhile; it runs only on a click. In server mode both writes are an admin's (`/settings` is in `ADMIN_WRITE_PREFIXES`).
|
||||
---
|
||||
|
||||
### Webhooks
|
||||
|
||||
```http
|
||||
GET /api/webhooks/providers
|
||||
GET /api/webhooks
|
||||
POST /api/webhooks
|
||||
PATCH /api/webhooks/:id
|
||||
DELETE /api/webhooks/:id
|
||||
POST /api/webhooks/:id/test
|
||||
GET /api/webhooks/:id/deliveries?limit=&offset=
|
||||
```
|
||||
|
||||
Where fired alerts are delivered (`server/lib/webhook-providers.js`). `providers` → `{ providers }`, the catalog with each provider's fields (secrets flagged). A target is answered REDACTED: `{ id, name, type, enabled, url_preview (masked), has_secret, headers (values redacted, generic family only), config (secret fields as ••••), rule_ids, created_at, updated_at, last_delivery }`. `POST` body `{ name, type, url?, enabled?, secret?, headers?, rule_ids?, config? }` — `type` one of the catalog ids (`slack`, `discord`, `teams`, `google_chat`, `mattermost`, `rocketchat`, `telegram`, `pagerduty`, `opsgenie`, `splunk_oncall`, `zapier`, `make`, `pipedream`, `generic`); `url` is required for providers that need one; `secret`/`headers` apply to the generic family only; `rule_ids` limits it to those alert rules (omit = all) → `201 { target }`. `PATCH` changes only the keys present (`config` is merged over the stored one, so a secret need not be re-sent) → `{ target }`. `DELETE` → `{ ok: true }` (its delivery log cascades). `test` sends a synthetic alert and always answers 200 `{ ok, status, attempts, error }`. `deliveries` → `{ deliveries, limit, offset }` (`limit` 1–200, default 20). `400 INVALID_INPUT`, `404 NOT_FOUND`. Staff-only in server mode, writes admin-only.
|
||||
|
||||
### Web Push
|
||||
|
||||
```http
|
||||
GET /api/push/vapid-public-key
|
||||
POST /api/push/subscribe
|
||||
DELETE /api/push/subscribe
|
||||
POST /api/push/send
|
||||
```
|
||||
|
||||
`vapid-public-key` → `{ publicKey }` for `PushManager.subscribe`. `subscribe` stores a browser subscription `{ endpoint, keys: { p256dh, auth } }` (upsert by `endpoint`; in server mode tagged with the user) → `{ ok: true }`; `DELETE` takes `{ endpoint }` in the BODY (a user removes only their own) → `{ ok: true }`. `send` `{ title, body, url? }` pushes to every subscription (in server mode: the caller's own) and fires a native notification in the desktop app → `{ ok: true, native, pushed, failed }`. `400` with `{ error: { message } }` on a missing field.
|
||||
|
||||
### Updates
|
||||
|
||||
```http
|
||||
GET /api/updates/status
|
||||
POST /api/updates/check
|
||||
POST /api/updates/apply
|
||||
```
|
||||
|
||||
Release-based update detection (`server/lib/update-check.js`). `status` and `check` answer the same object — `{ git_repo, update_available, install_kind: "checkout"|"plugin", local_version, latest_release, latest_release_url, latest_release_notes, release_check, commits_behind, commits_behind_release, manual_command, situation, last_plugin_update, repo_root, … }` (`repo_root` is `null` for a developer); `check` also broadcasts it as `update_status`. `apply` (admin-only in server mode) picks its mode by install kind, never from the request: a fast-forwardable checkout merges the release `--ff-only`, rebuilds, answers `{ …, applied: true }` and then restarts the server; a plugin-cache install with a newer release hands off to the detached plugin-update chain → `{ …, applied: true, mode: "plugin" }`. Nothing to apply is `409 UPDATE_NOT_APPLICABLE` (`reason`: `up_to_date`, `plugin_managed`, `not_fast_forwardable`). `500 UPDATE_STATUS_FAILED` / `UPDATE_CHECK_FAILED` / `UPDATE_APPLY_FAILED`.
|
||||
|
||||
### Import history
|
||||
|
||||
```http
|
||||
GET /api/import/guide
|
||||
POST /api/import/rescan
|
||||
POST /api/import/scan-path
|
||||
POST /api/import/upload
|
||||
```
|
||||
|
||||
Bring Claude Code history in (Settings → Import). `guide` → `{ platform, default_projects_dir, default_projects_dir_display, default_projects_dir_exists, default_projects_dir_stats: { projects, jsonl_files }, archive_command, supported_extensions, max_upload_bytes, max_upload_files, steps }`. `rescan` re-reads the default projects folder → `{ ok: true, source: "default", … }`. `scan-path` `{ path }` (absolute, `~` expanded, a directory) imports from it → `{ ok: true, source: "path", path, imported, skipped, backfilled, errors, sessions_seen, files_scanned }`; `400 INVALID_INPUT` / `PATH_NOT_FOUND` / `NOT_A_DIRECTORY`. `upload` takes multipart `files` (`.jsonl`, `.meta.json`, `.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`; up to `AGENTHUB_IMPORT_MAX_FILES` = 2000 files and `AGENTHUB_IMPORT_MAX_BYTES` = 1 GB), extracts archives into a private temp dir and imports → `{ ok: true, source: "upload", files_received, rejected_files, entries_extracted, entries_skipped, …counts }`; `400 NO_FILES` / `NO_JSONL`, `413` when an archive passes the extraction limits. Imports are idempotent, and progress is broadcast as `import.progress`. Staff-only in server mode, admin-only to run; `500 IMPORT_FAILED`.
|
||||
|
||||
|
||||
## WebSocket API
|
||||
|
||||
### Connection
|
||||
|
||||
Reference in New Issue
Block a user