# DO NOT EDIT BY HAND. Generated from server/openapi.js via `npm run openapi:yaml`. # This YAML mirrors the live spec served at GET /api/openapi.json. openapi: 3.0.3 info: title: Agent Dashboard for Claude Code API version: 1.4.6 description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence. contact: name: Nguyễn Ngọc Trí Vĩ email: vinnt@smartgift.vn url: https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor externalDocs: description: Project documentation url: https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor#readme servers: - url: http://localhost:4820 description: Local dashboard server (default) - url: http://127.0.0.1:4820 description: Local loopback endpoint used by hook-handler tags: - name: Health description: Service liveness checks - name: Metrics description: Prometheus / OpenMetrics scrape endpoint - name: Sessions description: Claude Code session lifecycle - name: Agents description: Main/subagent records and status - name: Events description: Event stream persistence - name: Stats description: High-level dashboard counters - name: Analytics description: Aggregated analytics views - name: Hooks description: Claude hook ingestion endpoint - name: Pricing description: Model pricing and token cost calculations - name: Workflows description: Workflow intelligence and session drill-in - name: Settings description: Operational maintenance endpoints - name: Updates description: Detect upstream git changes so users can pull and restart manually (local dashboard installs) - name: Alerts description: 'Rules-based alerting: rule CRUD, fired-alert feed, acknowledgement' - name: Webhooks description: 'Universal webhook delivery for fired alerts: target CRUD (Slack/Discord/Teams/generic), test probe, and delivery log. Secrets are never returned.' - name: Remote Sources description: Config for remote SSH machines the dashboard pulls Claude Code history from. No secrets are stored — SSH auth defers to the host's SSH stack (agent, ~/.ssh/config, keys). - name: Documentation description: OpenAPI/Swagger endpoints - name: CcConfig description: 'Claude Code configuration explorer: discover and (for low-risk artifacts) edit skills, agents, commands, output styles, MCP servers, hooks, settings, memory, plugins, marketplaces, keybindings, statusline' - name: Run description: Spawn and supervise Claude Code runs from the dashboard (headless or interactive conversation), stream output over WebSocket, and manage run history - name: Lanes description: Durable parallel-work lanes and dashboard-managed git worktrees - name: Push description: Web Push notification subscriptions and broadcast (VAPID); also fires native Electron notifications when hosted in the desktop app components: parameters: SessionIdPath: name: id in: path required: true schema: type: string description: Session ID AgentIdPath: name: id in: path required: true schema: type: string description: Agent ID PatternPath: name: pattern in: path required: true schema: type: string description: Model pattern (URL-encoded) LimitQuery: name: limit in: query required: false schema: type: integer minimum: 0 description: Page size OffsetQuery: name: offset in: query required: false schema: type: integer minimum: 0 description: Pagination offset SessionStatusQuery: name: status in: query required: false schema: type: string enum: - active - completed - error - abandoned description: Filter by session status AgentStatusQuery: name: status in: query required: false schema: type: string enum: - working - waiting - completed - error description: Filter by agent status SessionFilterQuery: name: session_id in: query required: false schema: type: string description: Filter by session ID WorkflowStatusQuery: name: status in: query required: false schema: type: string enum: - all - active - completed - error - abandoned description: Filter workflow aggregates by session status SourcesQuery: name: sources in: query required: false schema: type: string description: Comma-separated list of data-source ids to include. The built-in local history is the id `local`; remote SSH machines use their `remote_sources.id`. Omit to include every source. Filters on the `sessions.source` column. schemas: ErrorObject: type: object required: - code - message properties: code: type: string message: type: string ErrorResponse: type: object required: - error properties: error: $ref: '#/components/schemas/ErrorObject' MessageErrorObject: type: object required: - message properties: message: type: string MessageErrorResponse: type: object required: - error properties: error: $ref: '#/components/schemas/MessageErrorObject' CountMap: type: object additionalProperties: type: integer Session: type: object required: - id - status - started_at - updated_at properties: id: type: string name: type: string nullable: true status: type: string enum: - active - completed - error - abandoned cwd: type: string nullable: true model: type: string nullable: true started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true metadata: type: string nullable: true description: JSON-encoded session metadata updated_at: type: string format: date-time agent_count: type: integer nullable: true last_activity: type: string format: date-time nullable: true cost: type: number nullable: true awaiting_input_since: type: string format: date-time nullable: true description: ISO timestamp set when Claude Code is blocked waiting for the user (permission prompt or input request). Null when not waiting; cleared on the next non-Notification hook event. awaiting_reason: type: string enum: - notification - stop - session_start - interrupted nullable: true description: 'Why the session is awaiting input: ''notification'' (Claude asked the user something), ''stop'' (turn completed, waiting for the next prompt), ''session_start'' (new/resumed session waiting for the first prompt), or ''interrupted'' (watchdog/Esc recovery). Null when not waiting; cleared alongside awaiting_input_since.' Agent: type: object required: - id - session_id - name - type - status - started_at - updated_at properties: id: type: string session_id: type: string name: type: string type: type: string enum: - main - subagent subagent_type: type: string nullable: true status: type: string enum: - working - waiting - completed - error task: type: string nullable: true current_tool: type: string nullable: true started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true parent_agent_id: type: string nullable: true metadata: type: string nullable: true description: JSON-encoded agent metadata updated_at: type: string format: date-time awaiting_input_since: type: string format: date-time nullable: true description: ISO timestamp set when this agent is blocked waiting for user input. Cleared on the next non-Notification hook event for the session. awaiting_reason: type: string enum: - notification - stop - session_start - interrupted nullable: true description: 'Why this agent is awaiting input: ''notification'' (Claude asked the user something), ''stop'' (turn completed, waiting for the next prompt), ''session_start'' (new/resumed session waiting for the first prompt), or ''interrupted'' (watchdog/Esc recovery). Null when not waiting; cleared alongside awaiting_input_since.' DashboardEvent: type: object required: - session_id - event_type - created_at properties: id: type: integer nullable: true session_id: type: string agent_id: type: string nullable: true event_type: type: string tool_name: type: string nullable: true summary: type: string nullable: true data: type: string nullable: true description: JSON-encoded event payload created_at: type: string format: date-time HealthResponse: type: object required: - status - timestamp - version properties: status: type: string enum: - ok version: type: string description: Dashboard release version from package.json example: 1.4.6 timestamp: type: string format: date-time SessionsListResponse: type: object required: - sessions - limit - offset - total properties: sessions: type: array items: $ref: '#/components/schemas/Session' limit: type: integer offset: type: integer total: type: integer description: Total sessions matching the filters (independent of limit/offset). Used by paginators. SessionCreateRequest: type: object required: - id properties: id: type: string name: type: string cwd: type: string model: type: string metadata: type: object additionalProperties: true SessionCreateResponse: type: object required: - session - created properties: session: $ref: '#/components/schemas/Session' created: type: boolean SessionDetailResponse: type: object required: - session - agents - events properties: session: $ref: '#/components/schemas/Session' agents: type: array items: $ref: '#/components/schemas/Agent' events: type: array items: $ref: '#/components/schemas/DashboardEvent' TranscriptInfo: type: object required: - id - name - type - has_transcript properties: id: type: string description: '''main'' for the session''s main transcript, otherwise a subagent or compaction id.' name: type: string type: type: string enum: - main - subagent - compaction subagent_type: type: string nullable: true has_transcript: type: boolean db_agent_id: type: string nullable: true description: Foreign key into agents.id when this transcript belongs to a tracked subagent. TranscriptListResponse: type: object required: - transcripts properties: transcripts: type: array items: $ref: '#/components/schemas/TranscriptInfo' TranscriptContent: type: object required: - type properties: type: type: string enum: - text - tool_use - tool_result - thinking text: type: string name: type: string description: Tool name when type === tool_use. id: type: string description: Tool-use id used to pair tool_use with tool_result. input: description: 'Tool input payload (object) or { _truncated: string } when oversized.' oneOf: - type: object additionalProperties: true - type: string output: type: string description: Tool output text when type === tool_result. is_error: type: boolean TranscriptMessage: type: object required: - type - content properties: type: type: string enum: - user - assistant - session_event description: Raw line type. session_event is a synthetic marker (e.g. a /rename) injected by the server, not a real transcript line. Mid-turn queued messages (queued_command attachments) surface as type user. sender: type: string enum: - user - assistant - orchestrator - system - tool description: 'TRUE sender of the line: the human (user), the agent (assistant), a subagent''s task assigned by its parent (orchestrator), a harness injection such as a task-notification or [SYSTEM NOTIFICATION …] banner (system), or a tool_result echoed on a user line (tool).' timestamp: type: string format: date-time nullable: true content: type: array items: $ref: '#/components/schemas/TranscriptContent' line: type: integer minimum: 1 description: JSONL line number the message was parsed from (pagination cursor). model: type: string usage: type: object properties: input_tokens: type: integer minimum: 0 output_tokens: type: integer minimum: 0 cache_read_input_tokens: type: integer minimum: 0 cache_creation_input_tokens: type: integer minimum: 0 event_kind: type: string enum: - rename description: 'For type === session_event: the TUI action this marker represents.' title: type: string description: 'For type === session_event: the new session title.' TranscriptResponse: type: object required: - messages - total - has_more - last_line - first_line properties: messages: type: array items: $ref: '#/components/schemas/TranscriptMessage' total: type: integer minimum: 0 description: Total messages available in the transcript. has_more: type: boolean description: True when older messages remain (use `before` to load them). last_line: type: integer minimum: 0 description: JSONL line number of the newest returned message — pass back as `after` for incremental fetches. first_line: type: integer minimum: 0 description: JSONL line number of the oldest returned message — pass back as `before` to page backwards. SessionStatsResponse: type: object description: Aggregated counts powering the SessionOverview panel on the Session Detail page. All aggregation runs in SQL. required: - session_id - total_events - events_by_type - tools_used - error_count - first_event_at - last_event_at - agents - subagent_types - tokens properties: session_id: type: string total_events: type: integer minimum: 0 events_by_type: type: array items: type: object required: - event_type - count properties: event_type: type: string count: type: integer minimum: 0 tools_used: type: array description: Top 15 tools used in this session, sorted by count descending. items: type: object required: - tool_name - count properties: tool_name: type: string count: type: integer minimum: 0 error_count: type: integer minimum: 0 description: Events whose event_type or summary matches /error/i or /failed/i (case-insensitive). first_event_at: type: string format: date-time nullable: true last_event_at: type: string format: date-time nullable: true agents: type: object required: - total - main - subagent - compaction - by_status properties: total: type: integer minimum: 0 main: type: integer minimum: 0 subagent: type: integer minimum: 0 compaction: type: integer minimum: 0 by_status: type: object additionalProperties: type: integer minimum: 0 subagent_types: type: array description: Subagent types in this session with counts. Excludes the special 'compaction' type which is surfaced via agents.compaction. items: type: object required: - subagent_type - count properties: subagent_type: type: string count: type: integer minimum: 0 tokens: type: object required: - input_tokens - output_tokens - cache_read_tokens - cache_write_tokens properties: input_tokens: type: integer minimum: 0 output_tokens: type: integer minimum: 0 cache_read_tokens: type: integer minimum: 0 cache_write_tokens: type: integer minimum: 0 SessionUpdateRequest: type: object properties: name: type: string status: type: string enum: - active - completed - error - abandoned ended_at: type: string format: date-time metadata: type: object additionalProperties: true SessionUpdateResponse: type: object required: - session properties: session: $ref: '#/components/schemas/Session' AgentsListResponse: type: object required: - agents - limit - offset properties: agents: type: array items: $ref: '#/components/schemas/Agent' limit: type: integer offset: type: integer AgentCreateRequest: type: object required: - id - session_id - name properties: id: type: string session_id: type: string name: type: string type: type: string enum: - main - subagent subagent_type: type: string status: type: string enum: - working - waiting - completed - error task: type: string parent_agent_id: type: string metadata: type: object additionalProperties: true AgentCreateResponse: type: object required: - agent - created properties: agent: $ref: '#/components/schemas/Agent' created: type: boolean AgentDetailResponse: type: object required: - agent properties: agent: $ref: '#/components/schemas/Agent' AgentUpdateRequest: type: object properties: name: type: string status: type: string enum: - working - waiting - completed - error task: type: string current_tool: type: string nullable: true ended_at: type: string format: date-time metadata: type: object additionalProperties: true AgentUpdateResponse: type: object required: - agent properties: agent: $ref: '#/components/schemas/Agent' EventsListResponse: type: object required: - events - limit - offset - total properties: events: type: array items: $ref: '#/components/schemas/DashboardEvent' limit: type: integer offset: type: integer total: type: integer description: Total rows matching the current filter (for UI pagination) EventsFacetsResponse: type: object required: - event_types - tool_names properties: event_types: type: array items: type: string tool_names: type: array items: type: string StatsResponse: type: object required: - total_sessions - active_sessions - active_agents - total_agents - total_events - events_today - ws_connections - agents_by_status - sessions_by_status properties: total_sessions: type: integer active_sessions: type: integer active_agents: type: integer total_agents: type: integer total_events: type: integer events_today: type: integer ws_connections: type: integer agents_by_status: $ref: '#/components/schemas/CountMap' sessions_by_status: $ref: '#/components/schemas/CountMap' AnalyticsResponse: type: object required: - tokens - tool_usage - daily_events - daily_sessions - agent_types - event_types - avg_events_per_session - total_subagents - overview - agents_by_status - sessions_by_status properties: tokens: type: object required: - total_input - total_output - total_cache_read - total_cache_write properties: total_input: type: integer total_output: type: integer total_cache_read: type: integer total_cache_write: type: integer tool_usage: type: array items: type: object required: - tool_name - count properties: tool_name: type: string count: type: integer daily_events: type: array items: type: object required: - date - count properties: date: type: string count: type: integer daily_sessions: type: array items: type: object required: - date - count properties: date: type: string count: type: integer agent_types: type: array items: type: object required: - subagent_type - count properties: subagent_type: type: string nullable: true count: type: integer event_types: type: array items: type: object required: - event_type - count properties: event_type: type: string count: type: integer avg_events_per_session: type: number total_subagents: type: integer overview: type: object required: - total_sessions - active_sessions - active_agents - total_agents - total_events properties: total_sessions: type: integer active_sessions: type: integer active_agents: type: integer total_agents: type: integer total_events: type: integer agents_by_status: $ref: '#/components/schemas/CountMap' sessions_by_status: $ref: '#/components/schemas/CountMap' HookEventRequest: type: object required: - hook_type - data properties: hook_type: type: string description: 'Hook type from Claude Code (common values: PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd)' data: type: object required: - session_id properties: session_id: type: string tool_name: type: string transcript_path: type: string additionalProperties: true HookEventResponse: type: object required: - ok - event properties: ok: type: boolean enum: - true event: $ref: '#/components/schemas/DashboardEvent' PricingRule: type: object required: - model_pattern - display_name - input_per_mtok - output_per_mtok - cache_read_per_mtok - cache_write_per_mtok - cache_write_1h_per_mtok - updated_at properties: model_pattern: type: string display_name: type: string input_per_mtok: type: number output_per_mtok: type: number cache_read_per_mtok: type: number cache_write_per_mtok: type: number description: 5m ephemeral cache-write rate cache_write_1h_per_mtok: type: number description: 1h ephemeral cache-write rate fast_input_per_mtok: type: number description: Fast-mode input rate (0 = none) fast_output_per_mtok: type: number description: Fast-mode output rate (0 = none) updated_at: type: string format: date-time PricingUpsertRequest: type: object required: - model_pattern - display_name properties: model_pattern: type: string display_name: type: string input_per_mtok: type: number output_per_mtok: type: number cache_read_per_mtok: type: number cache_write_per_mtok: type: number description: 5m ephemeral cache-write rate cache_write_1h_per_mtok: type: number description: 1h ephemeral cache-write rate fast_input_per_mtok: type: number description: Fast-mode input rate (0 = none) fast_output_per_mtok: type: number description: Fast-mode output rate (0 = none) PricingListResponse: type: object required: - pricing properties: pricing: type: array items: $ref: '#/components/schemas/PricingRule' PricingUpsertResponse: type: object required: - pricing properties: pricing: $ref: '#/components/schemas/PricingRule' CostBreakdownItem: type: object required: - model - input_tokens - output_tokens - cache_read_tokens - cache_write_tokens - cost - matched_rule properties: model: type: string speed: type: string inference_geo: type: string service_tier: type: string input_tokens: type: integer output_tokens: type: integer cache_read_tokens: type: integer cache_write_tokens: type: integer cache_write_1h_tokens: type: integer web_search_requests: type: integer web_fetch_requests: type: integer code_execution_requests: type: integer cost: type: number matched_rule: type: string nullable: true DailyCostItem: type: object required: - date - cost properties: date: type: string format: date cost: type: number CostResult: type: object required: - total_cost - breakdown - daily_costs properties: total_cost: type: number breakdown: type: array items: $ref: '#/components/schemas/CostBreakdownItem' daily_costs: type: array items: $ref: '#/components/schemas/DailyCostItem' feature_costs: type: object description: Server-tool surcharges separate from token cost properties: web_search_cost: type: number web_fetch_cost: type: number code_execution_cost: type: number code_execution_hours_estimated: type: number code_execution_free_hours: type: number unpriced_models: type: array description: Models with usage but no matching pricing rule (cost not counted) items: type: object properties: model: type: string input_tokens: type: integer output_tokens: type: integer cache_read_tokens: type: integer cache_write_tokens: type: integer DeleteOkResponse: type: object required: - ok properties: ok: type: boolean enum: - true WorkflowAggregateResponse: type: object required: - stats - orchestration - toolFlow - effectiveness - patterns - modelDelegation - errorPropagation - concurrency - complexity - compaction - cooccurrence properties: stats: type: object required: - totalSessions - totalAgents - totalSubagents - avgSubagents - successRate - avgDepth - avgDurationSec - totalCompactions - avgCompactions properties: totalSessions: type: integer totalAgents: type: integer totalSubagents: type: integer avgSubagents: type: number successRate: type: number avgDepth: type: number avgDurationSec: type: integer totalCompactions: type: integer avgCompactions: type: number topFlow: type: object nullable: true properties: source: type: string target: type: string count: type: integer orchestration: type: object additionalProperties: true toolFlow: type: object additionalProperties: true effectiveness: type: array items: type: object additionalProperties: true patterns: type: object additionalProperties: true modelDelegation: type: object additionalProperties: true errorPropagation: type: object additionalProperties: true concurrency: type: object additionalProperties: true complexity: type: array items: type: object additionalProperties: true compaction: type: object additionalProperties: true cooccurrence: type: array items: type: object additionalProperties: true AgentTreeNode: type: object required: - id - name - type - status - children properties: id: type: string name: type: string type: type: string enum: - main - subagent subagent_type: type: string nullable: true status: type: string task: type: string nullable: true started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true children: type: array items: $ref: '#/components/schemas/AgentTreeNode' WorkflowSessionResponse: type: object required: - session - tree - toolTimeline - swimLanes - events properties: session: $ref: '#/components/schemas/Session' tree: type: array items: $ref: '#/components/schemas/AgentTreeNode' toolTimeline: type: array items: type: object properties: id: type: integer tool_name: type: string event_type: type: string agent_id: type: string nullable: true created_at: type: string format: date-time summary: type: string nullable: true swimLanes: type: array items: type: object properties: id: type: string name: type: string type: type: string subagent_type: type: string nullable: true status: type: string started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true parent_agent_id: type: string nullable: true events: type: array items: $ref: '#/components/schemas/DashboardEvent' SettingsInfoResponse: type: object required: - db - hooks - server - transcript_cache properties: db: type: object required: - path - size - counts properties: path: type: string size: type: integer counts: type: object additionalProperties: type: integer hooks: type: object required: - installed - path - hooks properties: installed: type: boolean path: type: string hooks: type: object additionalProperties: type: boolean server: type: object required: - version - uptime - node_version - platform - ws_connections properties: version: type: string description: Dashboard release version from package.json uptime: type: number node_version: type: string platform: type: string ws_connections: type: integer transcript_cache: type: object required: - entries - paths properties: entries: type: integer paths: type: array items: type: string ClearDataResponse: type: object required: - ok - cleared properties: ok: type: boolean enum: - true cleared: type: object additionalProperties: type: integer ReimportResponse: type: object required: - ok - imported - skipped - errors properties: ok: type: boolean enum: - true imported: type: integer skipped: type: integer errors: type: integer ImportGuideResponse: type: object properties: platform: type: string default_projects_dir: type: string default_projects_dir_display: type: string default_projects_dir_exists: type: boolean default_projects_dir_stats: type: object properties: projects: type: integer jsonl_files: type: integer archive_command: type: string supported_extensions: type: array items: type: string max_upload_bytes: type: integer max_upload_files: type: integer steps: type: array items: type: object properties: id: type: string title: type: string body: type: string ImportResultResponse: type: object required: - ok - source - imported - skipped - errors properties: ok: type: boolean enum: - true source: type: string enum: - default - path - upload path: type: string nullable: true imported: type: integer backfilled: type: integer skipped: type: integer errors: type: integer sessions_seen: type: integer files_scanned: type: integer files_received: type: integer rejected_files: type: array items: type: string entries_extracted: type: integer entries_skipped: type: integer ReinstallHooksResponse: type: object required: - ok - hooks properties: ok: type: boolean hooks: type: object required: - installed - path - hooks properties: installed: type: boolean path: type: string hooks: type: object additionalProperties: type: boolean ResetPricingResponse: type: object required: - ok - pricing properties: ok: type: boolean enum: - true pricing: type: array items: $ref: '#/components/schemas/PricingRule' ExportResponse: type: object required: - exported_at - sessions - agents - events - token_usage - model_pricing properties: format: type: string description: Bundle format marker (always "ccam-export" for exports from this version). example: ccam-export version: type: integer description: Bundle schema version. example: 1 exported_at: type: string format: date-time sessions: type: array items: $ref: '#/components/schemas/Session' agents: type: array items: $ref: '#/components/schemas/Agent' events: type: array items: $ref: '#/components/schemas/DashboardEvent' token_usage: type: array items: type: object additionalProperties: true workflows: type: array items: type: object additionalProperties: true dashboard_runs: type: array items: type: object additionalProperties: true alert_rules: type: array items: type: object additionalProperties: true model_pricing: type: array items: $ref: '#/components/schemas/PricingRule' ImportResponse: type: object required: - ok - sessions_imported - sessions_skipped - agents - events - token_usage - workflows - dashboard_runs - alert_rules - model_pricing - errors description: Per-table counts from restoring an export bundle. Session-scoped counts are rows newly inserted; sessions_skipped counts sessions already present (skipped whole to stay idempotent). properties: ok: type: boolean source: type: string description: Uploaded filename or server-side path read. format: type: string nullable: true description: Bundle format marker, or null for a legacy export. sessions_imported: type: integer sessions_skipped: type: integer agents: type: integer events: type: integer token_usage: type: integer workflows: type: integer dashboard_runs: type: integer alert_rules: type: integer model_pricing: type: integer errors: type: integer CleanupRequest: type: object properties: abandon_hours: type: number minimum: 0 description: Mark active sessions abandoned if stale for this many hours purge_days: type: number minimum: 0 description: Delete old completed/error/abandoned sessions older than this many days CleanupResponse: type: object required: - ok - abandoned - purged_sessions - purged_events - purged_agents properties: ok: type: boolean enum: - true abandoned: type: integer purged_sessions: type: integer purged_events: type: integer purged_agents: type: integer CcConfigScope: type: string enum: - user - project - all description: Discovery scope. `user` = ~/.claude (or CLAUDE_HOME); `project` = /.claude (and project CLAUDE.md); `all` = both, merged. Defaults to `all` when omitted or unrecognized. example: all CcConfigFrontmatter: type: object additionalProperties: type: string description: Parsed YAML frontmatter from the artifact's markdown header (key → string value). Empty object when the file has no parseable frontmatter. Multi-line values are preserved as raw strings; quotes are stripped. example: name: code-reviewer description: Reviews diffs for regressions model: sonnet CcConfigSkill: type: object description: 'A discovered skill: a directory containing a SKILL.md file under /skills//.' required: - scope - name - path - file - size - mtime - truncated - frontmatter - preview properties: scope: type: string enum: - user - project description: Which root this skill was discovered under. example: user name: type: string description: Skill directory name. example: code-reviewer path: type: string description: Absolute path to the skill directory. example: /Users/son/.claude/skills/code-reviewer file: type: string description: Absolute path to the SKILL.md file. example: /Users/son/.claude/skills/code-reviewer/SKILL.md size: type: integer description: Size of SKILL.md in bytes. example: 2048 mtime: type: number description: Last-modified time of SKILL.md in epoch milliseconds. example: 1718900000000 truncated: type: boolean description: True when SKILL.md exceeds 256 KiB and the read was truncated to the first 256 KiB. example: false frontmatter: $ref: '#/components/schemas/CcConfigFrontmatter' preview: type: string description: First 320 characters of the markdown body (after frontmatter). example: Use this skill to review pull requests for correctness and style... CcConfigMdItem: type: object description: A single-file markdown artifact (subagent, slash command, or output style) found under //.md. required: - scope - name - file - size - mtime - truncated - frontmatter - preview properties: scope: type: string enum: - user - project description: Which root this artifact was discovered under. example: project name: type: string description: Artifact name (filename with the trailing .md stripped). example: backend-reviewer file: type: string description: Absolute path to the .md file. example: /repo/.claude/agents/backend-reviewer.md size: type: integer description: File size in bytes. example: 1536 mtime: type: number description: Last-modified time in epoch milliseconds. example: 1718900000000 truncated: type: boolean description: True when the file exceeded 256 KiB and was truncated. example: false frontmatter: $ref: '#/components/schemas/CcConfigFrontmatter' preview: type: string description: First 320 characters of the markdown body (after frontmatter). example: Review backend route and hook logic for regressions... CcConfigPluginContributions: type: object nullable: true description: Counts of artifacts a plugin contributes, plus its parsed .claude-plugin/plugin.json. Null when the plugin's installPath does not exist on disk. required: - skills - agents - commands - outputStyles - hooks - pluginJson properties: skills: type: integer description: Number of skill directories (containing SKILL.md) under the plugin. example: 3 agents: type: integer description: Number of .md agent files under the plugin. example: 2 commands: type: integer description: Number of .md command files under the plugin. example: 5 outputStyles: type: integer description: Number of .md output-style files under the plugin. example: 0 hooks: type: integer description: Number of files under the plugin's hooks/ directory. example: 1 pluginJson: type: object nullable: true additionalProperties: true description: Parsed contents of .claude-plugin/plugin.json, or null when absent/unparseable. example: name: superpowers version: 1.2.0 CcConfigPlugin: type: object description: An installed plugin instance derived from the plugins manifest. required: - key - name - marketplace - scope - version - installPath - installedAt - lastUpdated - gitCommitSha - installPathExists - enabled - contributes properties: key: type: string description: Manifest key for this plugin, either the bare name or `@`. example: superpowers@obra name: type: string description: Plugin name (the portion of `key` before any `@`). example: superpowers marketplace: type: string nullable: true description: Marketplace portion of `key` (after `@`), or null when unscoped. example: obra scope: type: string description: Install scope reported by the manifest instance (defaults to `user`). example: user version: type: string nullable: true description: Installed plugin version, or null. example: 1.2.0 installPath: type: string nullable: true description: Absolute install path, or null. example: /Users/son/.claude/plugins/superpowers installedAt: type: string nullable: true description: Install timestamp from the manifest, or null. example: '2026-05-01T12:00:00.000Z' lastUpdated: type: string nullable: true description: Last-updated timestamp from the manifest, or null. example: '2026-06-01T09:30:00.000Z' gitCommitSha: type: string nullable: true description: Pinned git commit SHA from the manifest, or null. example: a1b2c3d4e5f6 installPathExists: type: boolean description: True when installPath resolves to an existing directory on disk. example: true enabled: type: boolean nullable: true description: 'Tri-state enabled flag from settings.json `enabledPlugins`: true, false, or null when the plugin is not listed there.' example: true contributes: $ref: '#/components/schemas/CcConfigPluginContributions' CcConfigPluginsResponse: type: object description: Plugins manifest summary plus the resolved plugin list (user scope only). required: - manifestPath - manifestExists - plugins properties: manifestPath: type: string description: Absolute path to plugins/installed_plugins.json under the Claude home. example: /Users/son/.claude/plugins/installed_plugins.json manifestExists: type: boolean description: True when the manifest file was found and parsed. example: true plugins: type: array description: Plugins sorted by `key`. items: $ref: '#/components/schemas/CcConfigPlugin' CcConfigMcpServer: type: object description: A summarized MCP server definition. Sensitive details (header values, env values) are NOT returned — only their key NAMES are surfaced. required: - name - source - kind properties: name: type: string description: Server name (object key in the source config). example: github source: type: string description: Where the definition was found, e.g. `~/.claude.json (top-level)`, `~/.claude.json (projects[])`, or `~/.claude/settings.json`. example: ~/.claude.json (top-level) kind: type: string enum: - http - stdio - unknown description: 'Transport kind inferred from the definition: `http` (has url), `stdio` (has command), or `unknown`.' example: stdio url: type: string description: Endpoint URL — present only for `http` servers. example: https://mcp.example.com/sse headers: type: array items: type: string description: Header NAMES (values redacted) — present only for `http` servers. Empty array when no headers. example: - Authorization command: type: string description: Executable — present only for `stdio` servers. example: npx args: type: array items: type: string description: Command arguments — present only for `stdio` servers. example: - '-y' - '@modelcontextprotocol/server-github' envNames: type: array items: type: string description: Environment-variable NAMES (values redacted) — present only for `stdio` servers. example: - GITHUB_TOKEN CcConfigMcpResponse: type: object description: 'MCP servers split by where they apply: globally to the user, or scoped to the resolved project (cwd) in ~/.claude.json projects[].' required: - user - projectScoped properties: user: type: array description: User/global MCP servers (top-level ~/.claude.json plus ~/.claude/settings.json). items: $ref: '#/components/schemas/CcConfigMcpServer' projectScoped: type: array description: MCP servers scoped to the current project root in ~/.claude.json. items: $ref: '#/components/schemas/CcConfigMcpServer' CcConfigHookEntry: type: object description: A single flattened hook command bound to a matcher. required: - matcher - type - command - timeout properties: matcher: type: string description: Tool/event matcher pattern (defaults to `*`). example: Bash type: type: string description: Hook type (defaults to `command`). example: command command: type: string nullable: true description: Shell command to run, or null. example: node ~/.claude/hooks/handler.js timeout: type: integer nullable: true description: Per-hook timeout in seconds, or null when unset. example: 30 CcConfigHookSource: type: object description: Hooks read from one settings file. `hooks` is keyed by event name (e.g. PreToolUse, PostToolUse, Stop); each known-event value is an array of flattened hook entries, while unknown events are passed through verbatim. required: - scope - file - exists - hooks properties: scope: type: string enum: - user - project - project-local description: Which settings file this hook block came from. example: user file: type: string description: Absolute path to the settings file. example: /Users/son/.claude/settings.json exists: type: boolean description: True when the settings file was found and parsed. example: true hooks: type: object description: Event name → array of hook entries (for the known event types) or the raw matcher array (for unknown events). Only non-empty events are included. additionalProperties: type: array items: $ref: '#/components/schemas/CcConfigHookEntry' example: PreToolUse: - matcher: Bash type: command command: node guard.js timeout: null CcConfigSettingsSource: type: object description: One settings file's redacted contents. Secret-like keys (matching /token|secret|password|api[_-]?key|auth/i with string values) are replaced with the literal string ``. required: - scope - file - exists properties: scope: type: string enum: - user - project - project-local description: Which settings file this entry represents. example: user file: type: string description: Absolute path to the settings file. example: /Users/son/.claude/settings.json exists: type: boolean description: True when the file was found and parsed. When false, `data`/`raw_size` are omitted. example: true data: type: object additionalProperties: true description: Parsed, secret-redacted settings JSON. Present only when `exists` is true. example: model: sonnet apiKey: statusLine: type: command raw_size: type: integer description: Byte length of the raw file contents. Present only when `exists` is true. example: 412 CcConfigMemoryItem: type: object description: 'A memory artifact: either a primary CLAUDE.md (scope `user`/`project`) or a per-project file-based auto-memory markdown file (scope `auto-memory`). Auto-memory items additionally carry `project`, `name`, `isIndex`, and parsed `frontmatter`.' required: - scope - file - size - mtime - truncated - preview properties: scope: type: string enum: - user - project - auto-memory description: '`user`/`project` = the two primary CLAUDE.md files; `auto-memory` = a *.md file under ~/.claude/projects//memory/.' example: auto-memory project: type: string description: 'Auto-memory only: the ~/.claude/projects/ directory name the file belongs to.' example: '-Users-son-repo' name: type: string description: 'Auto-memory only: the markdown filename.' example: MEMORY.md isIndex: type: boolean description: 'Auto-memory only: true for index/manifest files (MEMORY.md, INDEX-*.md), which sort first.' example: true file: type: string description: Absolute path to the memory file. example: /Users/son/.claude/projects/-Users-son-repo/memory/MEMORY.md size: type: integer description: File size in bytes. example: 980 mtime: type: number description: Last-modified time in epoch milliseconds. example: 1718900000000 truncated: type: boolean description: True when the file exceeded 256 KiB and was truncated. example: false frontmatter: $ref: '#/components/schemas/CcConfigFrontmatter' preview: type: string description: First 480 characters of the body (frontmatter stripped for auto-memory files; raw head for CLAUDE.md). example: '## Persistent facts\n- Never run destructive ops without confirmation...' CcConfigMarketplace: type: object description: A known plugin marketplace and (best-effort) its parsed manifest summary. required: - name - source - installLocation - lastUpdated - pluginCount - marketplaceName - marketplaceDescription - marketplaceOwner properties: name: type: string description: Marketplace key/name. example: obra source: type: object nullable: true additionalProperties: true description: Source descriptor object (e.g. git/github source), or null. example: source: github repo: obra/superpowers-marketplace installLocation: type: string nullable: true description: Absolute path where the marketplace is checked out, or null. example: /Users/son/.claude/plugins/marketplaces/obra lastUpdated: type: string nullable: true description: Last-updated timestamp, or null. example: '2026-06-10T00:00:00.000Z' pluginCount: type: integer nullable: true description: Number of plugins in the marketplace manifest, or null when no manifest was readable. example: 12 marketplaceName: type: string nullable: true description: '`name` from the marketplace manifest, or null.' example: Superpowers marketplaceDescription: type: string nullable: true description: '`description` from the marketplace manifest, or null.' example: Curated agent skills marketplaceOwner: description: '`owner` from the marketplace manifest (object or string), or null.' nullable: true oneOf: - type: object additionalProperties: true - type: string example: name: obra CcConfigMarketplacesResponse: type: object description: Known-marketplaces manifest summary plus the resolved marketplace list. required: - knownPath - knownExists - items properties: knownPath: type: string description: Absolute path to plugins/known_marketplaces.json under the Claude home. example: /Users/son/.claude/plugins/known_marketplaces.json knownExists: type: boolean description: True when the known-marketplaces file was found and parsed. example: true items: type: array description: Marketplaces sorted by name. items: $ref: '#/components/schemas/CcConfigMarketplace' CcConfigKeybinding: type: object description: A single key → action binding. required: - key - action properties: key: type: string description: Key chord. example: ctrl+s action: type: string description: Action name (stringified). example: submit CcConfigKeybindingGroup: type: object description: A context-scoped group of keybindings. required: - context - bindings properties: context: type: string description: The context the bindings apply in (empty string when global). example: editor bindings: type: array items: $ref: '#/components/schemas/CcConfigKeybinding' CcConfigKeybindingsResponse: type: object description: 'Parsed ~/.claude/keybindings.json. When the file is absent, only `{ file, exists: false }` is returned.' required: - file - exists properties: file: type: string description: Absolute path to keybindings.json. example: /Users/son/.claude/keybindings.json exists: type: boolean description: True when the file was found and parsed. example: true schema: type: string nullable: true description: '`$schema` value from the file, or null. Present only when `exists` is true.' example: https://json.schemastore.org/claude-keybindings docs: type: string nullable: true description: '`$docs` value from the file, or null. Present only when `exists` is true.' example: https://docs.claude.com/keybindings groups: type: array description: Binding groups. Present only when `exists` is true. items: $ref: '#/components/schemas/CcConfigKeybindingGroup' CcConfigStatuslineScript: type: object description: A statusline script (statusline.py or statusline-command.sh) discovered under the Claude home. required: - file - size - mtime - truncated - preview properties: file: type: string description: Absolute path to the script. example: /Users/son/.claude/statusline.py size: type: integer description: File size in bytes. example: 1280 mtime: type: number description: Last-modified time in epoch milliseconds. example: 1718900000000 truncated: type: boolean description: True when the file exceeded 256 KiB and was truncated. example: false preview: type: string description: First 4000 characters of the script body. example: '#!/usr/bin/env python3\nimport json, sys\n...' CcConfigStatuslineResponse: type: object description: The statusLine config block from user settings.json plus any statusline scripts. required: - config - scripts properties: config: type: object nullable: true additionalProperties: true description: The `statusLine` object from ~/.claude/settings.json, or null when unset. example: type: command command: python3 ~/.claude/statusline.py scripts: type: array items: $ref: '#/components/schemas/CcConfigStatuslineScript' CcConfigHookScript: type: object description: A file in the ~/.claude/hooks/ directory. required: - name - file - size - mtime properties: name: type: string description: File name. example: post-tool-use.js file: type: string description: Absolute path to the script. example: /Users/son/.claude/hooks/post-tool-use.js size: type: integer description: File size in bytes. example: 640 mtime: type: number description: Last-modified time in epoch milliseconds. example: 1718900000000 CcConfigHookScriptsResponse: type: object description: Listing of the ~/.claude/hooks/ directory (files only, sorted by name). required: - dir - items properties: dir: type: string description: Absolute path to the hooks directory. example: /Users/son/.claude/hooks items: type: array items: $ref: '#/components/schemas/CcConfigHookScript' CcConfigScopeCount: type: object description: Per-scope counts (user vs project). required: - user - project properties: user: type: integer description: Count under the user root. example: 12 project: type: integer description: Count under the project root. example: 3 CcConfigOverviewResponse: type: object description: 'Top-level summary for the Claude Config Explorer landing page: resolved roots plus aggregate counts across every surface.' required: - roots - counts properties: roots: type: object description: The four filesystem roots the explorer reads from. required: - claudeHome - projectClaudeDir - projectRoot - claudeJson properties: claudeHome: type: string description: Claude home (CLAUDE_HOME or ~/.claude). example: /Users/son/.claude projectClaudeDir: type: string description: Project .claude directory under the resolved cwd. example: /repo/.claude projectRoot: type: string description: Resolved project root (the cwd). example: /repo claudeJson: type: string description: Path to ~/.claude.json (resolved from $HOME, not CLAUDE_HOME). example: /Users/son/.claude.json counts: type: object description: Aggregate counts by surface. required: - skills - agents - commands - outputStyles - plugins - pluginsEnabled - pluginsDisabled - marketplaces - keybindings - mcpServers - hooks - memory - settingsFiles properties: skills: $ref: '#/components/schemas/CcConfigScopeCount' agents: $ref: '#/components/schemas/CcConfigScopeCount' commands: $ref: '#/components/schemas/CcConfigScopeCount' outputStyles: $ref: '#/components/schemas/CcConfigScopeCount' plugins: type: integer description: Total plugins. example: 8 pluginsEnabled: type: integer description: Plugins explicitly enabled in settings. example: 6 pluginsDisabled: type: integer description: Plugins explicitly disabled in settings. example: 1 marketplaces: type: integer description: Known marketplaces. example: 2 keybindings: type: integer description: Total individual key bindings across all groups. example: 24 mcpServers: type: object description: MCP server counts by scope. required: - user - project properties: user: type: integer example: 3 project: type: integer example: 1 hooks: type: object description: Total hook entries per settings scope. required: - user - project - project-local properties: user: type: integer example: 2 project: type: integer example: 0 project-local: type: integer example: 1 memory: type: integer description: Total memory items. example: 5 settingsFiles: type: integer description: Number of settings files that exist (of the three scopes). example: 2 CcConfigSkillsResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigSkill' CcConfigAgentsResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigMdItem' CcConfigCommandsResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigMdItem' CcConfigOutputStylesResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigMdItem' CcConfigHooksResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigHookSource' CcConfigSettingsResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigSettingsSource' CcConfigMemoryResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/CcConfigMemoryItem' CcConfigFileReadResponse: type: object description: Body of a single file resolved under an allowed root. Returned only on success; failures return an ErrorResponse with HTTP 400. required: - ok - file - truncated - size - text - mtime properties: ok: type: boolean enum: - true description: Always true on success. example: true file: type: string description: Absolute, resolved path that was read. example: /Users/son/.claude/skills/code-reviewer/SKILL.md truncated: type: boolean description: True when the file exceeded 256 KiB and only the first 256 KiB are in `text`. example: false size: type: integer description: Full file size in bytes. example: 2048 text: type: string description: File contents (truncated to 256 KiB). example: '---\nname: code-reviewer\n---\n# Code Reviewer\n...' mtime: type: number description: Last-modified time in epoch milliseconds. example: 1718900000000 CcConfigFileWriteRequest: type: object description: Create-or-overwrite request for a low-risk text artifact. `name` is required for every type except `memory` (which targets CLAUDE.md). `project` is required for `auto-memory`. `scope` is ignored for `auto-memory` (always under the Claude home). required: - scope - type - content properties: scope: type: string enum: - user - project description: 'Target root: `user` (Claude home) or `project` (/.claude, or project root for memory). Ignored for `auto-memory`.' example: user type: type: string enum: - skills - agents - commands - output-styles - memory - auto-memory description: Artifact type. `skills` writes /skills//SKILL.md; `agents`/`commands`/`output-styles` write //.md; `memory` writes the scope's CLAUDE.md; `auto-memory` writes ~/.claude/projects//memory/. example: agents name: type: string description: Artifact name. Required for all types except `memory`. For skills/agents/commands/output-styles must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/. For auto-memory must be a flat *.md filename (/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/i, no `..`). example: my-reviewer content: type: string description: Full file contents to write. Must be a string and ≤ 256 KiB (UTF-8 bytes). example: '---\nname: my-reviewer\n---\n# My Reviewer\nReview the diff...' project: type: string description: 'Required for `auto-memory`: the ~/.claude/projects/ directory name. Validated against /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,255}$/ and rejected if it contains `..`.' example: '-Users-son-repo' CcConfigFileWriteResponse: type: object description: Result of a successful create-or-overwrite. required: - ok - file - target - backupPath - created properties: ok: type: boolean enum: - true example: true file: type: string description: Absolute path to the .md file that was written. example: /Users/son/.claude/agents/my-reviewer.md target: type: string description: Absolute path to the artifact target (the skill directory for skills; same as `file` otherwise). example: /Users/son/.claude/agents/my-reviewer.md backupPath: type: string nullable: true description: Absolute path to the timestamped backup created before overwriting, or null when this was a fresh create (nothing to back up). example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak created: type: boolean description: True when the artifact did not exist before this write. example: true CcConfigFileDeleteRequest: type: object description: Delete request for a low-risk text artifact. Same (scope, type, name, project) semantics as the write request, without `content`. required: - scope - type properties: scope: type: string enum: - user - project description: Target root. Ignored for `auto-memory`. example: user type: type: string enum: - skills - agents - commands - output-styles - memory - auto-memory description: Artifact type to delete. example: agents name: type: string description: Artifact name. Required for all types except `memory`. example: my-reviewer project: type: string description: 'Required for `auto-memory`: the projects/ directory name.' example: '-Users-son-repo' CcConfigFileDeleteResponse: type: object description: Result of a successful delete (a mandatory backup is created first). required: - ok - file - target - backupPath properties: ok: type: boolean enum: - true example: true file: type: string description: Absolute path to the .md file inside the deleted target. example: /Users/son/.claude/agents/my-reviewer.md target: type: string description: Absolute path to the deleted artifact (skill directory for skills). example: /Users/son/.claude/agents/my-reviewer.md backupPath: type: string nullable: true description: Absolute path to the backup taken before deletion. example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak CcConfigBackup: type: object description: A single backup entry under a cc-config-backups directory. required: - scope - type - name - backupPath - isDir - mtime - size properties: scope: type: string enum: - user - project - auto-memory description: Scope the backup belongs to. `auto-memory` backups are per-project. example: user project: type: string description: 'Auto-memory only: the projects/ directory name.' example: '-Users-son-repo' type: type: string enum: - skills - agents - commands - output-styles - memory - auto-memory description: Artifact type the backup was taken from. example: agents name: type: string description: Backup file/directory name (includes the timestamp and .bak suffix). example: my-reviewer.md.2026-06-25T12-00-00.000Z.bak backupPath: type: string description: Absolute path to the backup. example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak isDir: type: boolean description: True when the backup is a directory (skill backups). example: false mtime: type: number description: Backup modification time in epoch milliseconds (results sort newest first). example: 1718900000000 size: type: integer nullable: true description: Backup size in bytes, or null for directory backups. example: 2048 CcConfigBackupsResponse: type: object required: - items properties: items: type: array description: Backups across the requested scope/type buckets, sorted newest first. items: $ref: '#/components/schemas/CcConfigBackup' RunHandle: type: object description: Live, in-memory view of a spawned `claude` subprocess. Returned by GET /api/run (in `items`), POST /api/run (201), and GET /api/run/{id}. Backed by the spawner's handle map, which reaps each handle 5 minutes after the process exits — after that the run is only visible via GET /api/run/history. required: - id - pid - mode - cwd - model - permissionMode - effort - prompt - argv - resumeSessionId - status - startedAt - endedAt - exitCode - signal - error - sessionId - envelopeCount - stdoutTail - stderrTail properties: id: type: string format: uuid description: Server-generated run id (UUID v4). Distinct from the Claude session id. example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: type: integer nullable: true description: OS process id of the spawned `claude` child, or null if the spawn failed. example: 48213 mode: type: string enum: - headless - conversation description: '`headless`: single-shot — prompt is passed via argv `-p`, stdin is closed, the process exits after one turn. `conversation`: multi-turn — stdin stays open (`--input-format stream-json`) and follow-up turns are delivered via POST /api/run/{id}/message.' example: conversation cwd: type: string description: Absolute working directory the child was spawned in (sanitised at request time — must be an existing absolute directory). example: /Users/dev/projects/my-app model: type: string nullable: true description: Model alias/id passed via `--model`, or null to inherit the user's default. example: claude-opus-4-8 permissionMode: type: string enum: - acceptEdits - default - plan - bypassPermissions description: Permission mode passed via `--permission-mode`. Defaults to `acceptEdits` when omitted or invalid. example: acceptEdits effort: type: string enum: - low - medium - high - xhigh - max nullable: true description: Thinking-effort level passed via `--effort` (higher = more reasoning tokens before the assistant turn). Null inherits the model default. example: high prompt: type: string description: The initial user prompt. May be empty only when resuming a conversation (the child idles on the resumed transcript until a follow-up arrives). example: Refactor the auth module and add tests. argv: type: array items: type: string description: Exact argv vector passed to the `claude` binary (always includes `--output-format stream-json --verbose --include-partial-messages` plus the resolved flags). example: - '--output-format' - stream-json - '--verbose' - '--include-partial-messages' - '--permission-mode' - acceptEdits - '--input-format' - stream-json - '--model' - claude-opus-4-8 resumeSessionId: type: string nullable: true description: Claude session id this run resumed (`--resume`), or null for a fresh run. Conversation mode only. example: null status: type: string enum: - spawning - running - completed - error - killed description: Lifecycle status. `spawning` -> `running` on the first parsed output envelope; terminal states are `completed` (exit 0), `error` (non-zero exit or spawn error), or `killed` (terminated via DELETE). Each transition is broadcast as a `run_status` WebSocket message. example: running startedAt: type: integer format: int64 description: Spawn time in epoch milliseconds. example: 1718000000000 endedAt: type: integer format: int64 nullable: true description: Exit time in epoch milliseconds, or null while still live. example: null exitCode: type: integer nullable: true description: Process exit code once the child exits; null while running or if killed by signal. example: null signal: type: string nullable: true description: Terminating signal name (e.g. `SIGTERM`/`SIGKILL`) if the child was killed by a signal; null otherwise. example: null error: type: string nullable: true description: Spawn/runtime error message if the child failed to start or errored; null otherwise. example: null sessionId: type: string nullable: true description: Claude session id, captured from the `system/init` stream envelope (optimistically pre-seeded with resumeSessionId). Used to deep-link to /api/sessions/{id}. Null until the init envelope arrives. example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d envelopeCount: type: integer minimum: 0 description: Total stream-json envelopes parsed for this run so far (monotonic; not capped by the in-memory replay buffer). example: 12 stdoutTail: type: string description: Trailing slice of the child's raw stdout (capped at ~4 KiB) for quick diagnostics. example: | {"type":"result","subtype":"success",...} stderrTail: type: string description: Trailing slice of the child's raw stderr (capped at ~4 KiB); also captures parse errors. example: '' RunHandleWithEnvelopes: allOf: - $ref: '#/components/schemas/RunHandle' - type: object description: RunHandle plus the in-memory envelope replay buffer, returned by GET /api/run/{id}?envelopes=1. required: - envelopes properties: envelopes: type: array description: The most recent stream-json envelopes (capped at 500 per handle) passed through verbatim from the `claude` child — system/init, assistant text + tool_use, user tool_result, result/success, partial stream_event deltas, etc. Lets a late-attaching client replay what it missed. Full transcript is always available via /api/sessions/{id}. items: type: object additionalProperties: true RunListResponse: type: object description: All currently-tracked run handles (newest first) plus concurrency telemetry. required: - items - maxConcurrent - activeCount properties: items: type: array items: $ref: '#/components/schemas/RunHandle' description: Live + recently-exited handles, sorted by startedAt descending. Handles are reaped 5 minutes after exit. maxConcurrent: type: integer description: Concurrency cap (env `RUN_MAX_CONCURRENT`, default effectively uncapped at 10000). Spawns over this throw ECONCURRENCY (HTTP 429). example: 10000 activeCount: type: integer minimum: 0 description: Number of handles currently in `spawning` or `running` status. example: 2 RunHistoryItem: type: object description: A persisted run record from the `dashboard_runs` sqlite table. Survives the 5-minute in-memory reap so past runs remain visible and resumable. Fields are snake_case (DB column names) and timestamps are ISO-8601 strings, distinct from the camelCase epoch-ms fields on RunHandle. required: - id - session_id - mode - cwd - model - permission_mode - effort - resume_session_id - prompt_preview - status - exit_code - started_at - ended_at - isLive - lane_id properties: id: type: string format: uuid description: Run id (matches RunHandle.id). example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c session_id: type: string nullable: true description: Claude session id once known (from system/init), else null. example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d mode: type: string enum: - headless - conversation description: Run mode at spawn time. example: conversation cwd: type: string description: Absolute working directory the run was spawned in. example: /Users/dev/projects/my-app model: type: string nullable: true description: Model used, or null for the default. example: claude-opus-4-8 permission_mode: type: string nullable: true enum: - acceptEdits - default - plan - bypassPermissions description: Permission mode recorded at spawn time. example: acceptEdits effort: type: string nullable: true enum: - low - medium - high - xhigh - max description: Effort level recorded at spawn time, or null. example: high resume_session_id: type: string nullable: true description: Session id this run resumed, or null. example: null prompt_preview: type: string nullable: true description: First 500 characters of the initial prompt (truncated preview for the history list), or null. example: Refactor the auth module and add tests. status: type: string enum: - spawning - running - completed - error - killed - abandoned description: Persisted status. `abandoned` is set on server boot for rows left `running`/`spawning` by a previous process (their in-memory handles were wiped by the restart) so the UI does not show them as live. example: completed exit_code: type: integer nullable: true description: Recorded process exit code, or null. example: 0 started_at: type: string format: date-time description: Spawn time (ISO-8601). example: '2026-06-25T17:00:00.000Z' ended_at: type: string format: date-time nullable: true description: Exit time (ISO-8601), or null if still running / never recorded. example: '2026-06-25T17:01:42.500Z' isLive: type: boolean description: 'Computed at request time: true when a live in-memory handle for this id is currently `running` or `spawning`. Lets the UI mark which history rows are re-attachable.' example: false lane_id: type: integer nullable: true description: Lane this run was started through (POST /api/lanes/{id}/start), or null for a run spawned straight from POST /api/run. Filterable via ?laneId=. example: null RunHistoryResponse: type: object description: Persisted run history, most recent first. Empty `items` when the persistence DB is unavailable. required: - items properties: items: type: array items: $ref: '#/components/schemas/RunHistoryItem' RunCwdSuggestion: type: object description: A suggested working directory for spawning a run. Only directories that still exist on disk are returned. required: - kind - path - label properties: kind: type: string enum: - dashboard - home - recent description: '`dashboard`: the dashboard server''s own cwd (always first). `home`: $HOME. `recent`: distinct cwds Claude Code has been used in, sourced from the sessions table.' example: recent path: type: string description: Absolute, resolved directory path. example: /Users/dev/projects/my-app label: type: string description: Human-friendly label (defaults to the directory basename). example: my-app RunCwdsResponse: type: object description: Working-directory suggestions for the Run launcher. required: - items properties: items: type: array items: $ref: '#/components/schemas/RunCwdSuggestion' RunFilesResponse: type: object description: File-path autocomplete results for the prompt editor's `@` references — up to 40 paths relative to the given cwd, shortest first. required: - items properties: items: type: array items: type: string description: Paths relative to the resolved cwd. Dotfiles (except .env/.gitignore) and heavy build dirs (node_modules, .git, dist, build, etc.) are skipped. example: - package.json - src/index.ts - server/routes/run.js RunBinaryResponse: type: object description: Whether the `claude` binary is resolvable on PATH (probed via which/where; the binary is not invoked). Lets the UI warn before the user clicks Run. required: - found - path properties: found: type: boolean description: True when `claude` resolves on PATH. example: true path: type: string nullable: true description: Absolute path to the resolved `claude` binary, or null when not found. example: /usr/local/bin/claude RunSpawnRequest: type: object description: 'Request body for spawning a run. SIDE EFFECT: a successful call spawns a real `claude` process in `cwd`, begins streaming `run_stream`/`run_status` WebSocket messages, and persists a row to `dashboard_runs`.' required: - prompt properties: prompt: type: string description: Initial user prompt. Required and non-empty UNLESS mode is `conversation` AND resumeSessionId is set (a resumed conversation may start empty and idle on stdin). example: Refactor the auth module and add tests. mode: type: string enum: - headless - conversation default: conversation description: '`headless` for a single-shot run; `conversation` for a multi-turn run that accepts follow-ups via POST /api/run/{id}/message. Any value other than `headless` is treated as `conversation`.' example: conversation cwd: type: string description: Absolute working directory for the child. Must exist as a directory at request time. Omitted/empty defaults to the dashboard server's cwd. Non-absolute or missing paths are rejected with EBADCWD. example: /Users/dev/projects/my-app model: type: string description: Optional model alias/id passed via `--model`. Omit to inherit the user's default. example: claude-opus-4-8 resumeSessionId: type: string description: Optional Claude session id to resume via `--resume`. Must match /^[A-Za-z0-9-]{8,}$/ and requires conversation mode, else EBADSESSION / EBADMODE. example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d effort: type: string enum: - low - medium - high - xhigh - max description: Optional thinking-effort level passed via `--effort`. An invalid value is rejected with EBADEFFORT. example: high permissionMode: type: string enum: - acceptEdits - default - plan - bypassPermissions default: acceptEdits description: Permission mode passed via `--permission-mode`. Unknown values silently fall back to `acceptEdits`. example: acceptEdits RunSpawnConcurrencyResponse: type: object description: HTTP 429 body when the concurrency cap is reached (ECONCURRENCY). In addition to the standard `error` envelope, this response carries a top-level `running` array listing the runs currently occupying the cap. required: - error - running properties: error: $ref: '#/components/schemas/ErrorObject' running: type: array description: The live runs (status running/spawning) that are holding the concurrency slots. items: type: object required: - id - pid - startedAt - mode properties: id: type: string format: uuid example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: type: integer nullable: true example: 48213 startedAt: type: integer format: int64 example: 1718000000000 mode: type: string enum: - headless - conversation example: conversation RunMessageRequest: type: object description: 'Request body for sending a follow-up turn into a running conversation. SIDE EFFECT: writes a stream-json user envelope to the child''s stdin and broadcasts a `run_input_ack` WebSocket message.' required: - text properties: text: type: string description: The follow-up user message. Required and non-empty (else EBADINPUT). example: Also update the README to document the new flags. RunMessageResponse: type: object description: Acknowledgement that the follow-up turn was written to the child's stdin. required: - messageId properties: messageId: type: string format: uuid description: Server-generated id for this input turn; echoed in the `run_input_ack` WebSocket message. example: 9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f RunKillResponse: type: object description: Acknowledgement that a kill was issued (or that the run had already terminated). required: - ok properties: ok: type: boolean enum: - true example: true LaneWorktreeCreateRequest: type: object required: - sourceRepo properties: sourceRepo: type: string description: Existing absolute path to the source git repository. example: /Users/me/src/project title: type: string description: Human-readable lane title. example: Criteria form base: type: string description: Preferred base branch. Defaults to the LANE_BASE_BRANCH env var, or `main` when that is also unset. example: main slug: type: string description: Optional branch/directory slug override. example: criteria-form LaneEnsureRequest: type: object required: - cwd properties: cwd: type: string description: Absolute working directory to find or adopt a lane for. A lane whose own cwd is this path, or the closest path-boundary parent of it, is returned as-is. example: /Users/me/src/project/packages/app title: type: string description: Title for the lane if one has to be created; ignored when one already exists. example: App package LaneDestructiveActionRequest: type: object required: - confirm - expect properties: confirm: type: boolean enum: - true force: type: boolean description: Required by reset/remove when unpushed commits exist. expect: type: object description: 'Required complete facts returned by the preceding preflight: head, dirty, untracked, unpushed for reset/remove; sessions, events, tokenRows for purge. Differences return 409 ESTALE without destructive work.' additionalProperties: true LaneStageDetectionFields: type: object description: Fields the server's stage-detection heuristic (server/lib/stage-detect.js) adds to every lane returned by GET /api/lanes and GET /api/lanes/:id. An inferred stage is never evidence and never renders as done — see docs/LANES.md#stage-detection. properties: detected_stage: type: string nullable: true description: Stage id inferred from ingested tool events, or null if no signal has been seen. Independent of the agent's own declared `stage`. detected_signal: type: string nullable: true description: The tool-event signal that produced detected_stage, capped at 120 characters; null when detected_stage is null. detected: type: boolean description: Present on each entry of pipeline_nodes. True for the inferred node and any node before it that carries no declared record; decorates that node's state without ever upgrading it to done. PushVapidKeyResponse: type: object required: - publicKey description: The server's VAPID public key. The browser passes this base64url-encoded key to `PushManager.subscribe({ applicationServerKey })` so the push service will accept deliveries signed by this server's private key. properties: publicKey: type: string description: Base64url-encoded VAPID (P-256 ECDSA) public application server key. Generated once and persisted alongside the SQLite DB so the web app and native apps share one key pair. example: BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8 PushSubscriptionKeys: type: object required: - p256dh - auth description: Client encryption keys produced by the browser's PushManager subscription. Both are required to encrypt Web Push payloads for the endpoint. properties: p256dh: type: string description: Base64url-encoded P-256 ECDH public key from the browser subscription (`subscription.getKey('p256dh')`). Stored verbatim in the `push_subscriptions` table. example: BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM auth: type: string description: Base64url-encoded auth secret from the browser subscription (`subscription.getKey('auth')`). Stored verbatim in the `push_subscriptions` table. example: tBHItJI5svbpez7KI4CCXg PushSubscribeRequest: type: object required: - endpoint - keys description: A browser PushSubscription serialized for storage. Persisted via `INSERT OR REPLACE` keyed on `endpoint`, so re-subscribing the same endpoint is idempotent (it overwrites the stored keys rather than duplicating the row). properties: endpoint: type: string format: uri description: The push service delivery URL from `subscription.endpoint`. Acts as the primary key in `push_subscriptions`; sending later POSTs encrypted payloads here. Subscriptions that return HTTP 410 (Gone) during `/send` are pruned automatically. example: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ keys: $ref: '#/components/schemas/PushSubscriptionKeys' PushSubscribeResponse: type: object required: - ok description: Confirmation that the subscription was stored (or overwritten). properties: ok: type: boolean enum: - true description: Always `true` on success. example: true PushUnsubscribeRequest: type: object required: - endpoint description: 'Identifies the subscription to delete by its push-service endpoint. NOTE: the endpoint is supplied in the request BODY (DELETE with a JSON body), not as a query parameter.' properties: endpoint: type: string format: uri description: 'The `endpoint` of the subscription to remove from `push_subscriptions`. Deletion is idempotent — removing an endpoint that is not stored still returns `{ ok: true }`.' example: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ PushOkResponse: type: object required: - ok description: Generic success acknowledgement returned by subscribe/unsubscribe. properties: ok: type: boolean enum: - true description: Always `true` on success. example: true PushSendRequest: type: object required: - title - body description: Notification content to broadcast. Both fields are mandatory; a missing title or body yields a 400. The same title/body is delivered to every reachable surface (native Electron notification + all stored Web Push subscriptions). properties: title: type: string description: Notification title line. example: Session completed body: type: string description: Notification body text. example: Your Claude Code session finished with 3 subagents. PushSendResponse: type: object required: - ok - native - pushed - failed description: Reports which delivery surfaces actually fired. This lets the client distinguish a real delivery from a silent no-op (no subscribers AND no Electron host), which would otherwise look like success. properties: ok: type: boolean enum: - true description: Always `true` when dispatch ran without throwing. example: true native: type: boolean description: '`true` when a native OS notification was shown via Electron''s main-process Notification API (i.e. the server is hosted inside the desktop app and notifications are supported). `false` under a plain `npm start` host.' example: false pushed: type: integer minimum: 0 description: Count of stored Web Push subscriptions that accepted the encrypted payload (fulfilled `web-push` sends). example: 2 failed: type: integer minimum: 0 description: Count of Web Push sends that were rejected. Subscriptions rejected with HTTP 410 (Gone) are deleted from `push_subscriptions` as part of this request. example: 1 SessionsFacetsResponse: type: object description: 'Facet values for the Sessions page filter UI: the distinct working directories (cwd) and the distinct data-source ids seen across all sessions.' required: - cwds - sources properties: cwds: type: array description: Distinct, non-empty session working directories (the `cwd` column), sorted ascending. Powers the cwd filter dropdown. items: type: string example: - /Users/son/WebstormProjects/Claude-Code-Agent-Monitor - /Users/son/code/another-project sources: type: array description: Distinct data-source ids seen across all sessions (the `sessions.source` column). Always includes the built-in `local` history; each configured remote SSH machine contributes its `remote_sources.id`. Powers the source filter dropdown and the `sources` query param on the list/analytics endpoints. items: type: string example: - local - 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 RemoteSource: type: object description: A configured remote SSH machine the dashboard pulls Claude Code history from. NO secrets are stored on this record — SSH authentication defers entirely to the host's SSH stack (ssh-agent, `~/.ssh/config`, and key files). `host` is an SSH destination (`user@host`) or a `~/.ssh/config` alias. required: - id - label - host - ssh_port - identity_file - remote_home - enabled - status - last_error - last_sync_at - last_sync_counts - created_at - updated_at properties: id: type: string description: Primary key — the remote-source id (also used as `sessions.source`). example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 label: type: string description: Human-readable name shown in the UI. example: Work laptop host: type: string description: SSH destination (`user@host`) or a `~/.ssh/config` alias resolved by the host SSH stack. example: son@studio.local ssh_port: type: integer nullable: true description: Optional SSH port; null defers to the SSH default / `~/.ssh/config`. example: 22 identity_file: type: string nullable: true description: Optional path to a private-key file passed to ssh (`-i`); null to omit. example: ~/.ssh/id_ed25519 remote_home: type: string nullable: true description: Optional remote Claude home to read transcripts from; null defaults to the remote `~/.claude`. example: ~/.claude enabled: type: boolean description: Whether this source is eligible for scheduled/manual syncs. example: true status: type: string enum: - idle - syncing - ok - error description: Last known sync status of the source. example: ok last_error: type: string nullable: true description: Error message from the last failed sync/test, or null. example: null last_sync_at: type: string format: date-time nullable: true description: ISO-8601 timestamp of the last successful sync, or null. example: '2026-07-22T18:41:55.117Z' last_sync_counts: type: object nullable: true additionalProperties: true description: Counters from the last sync (imported / skipped / backfilled / errors / sessions_seen / sessions_tagged), or null if never synced. example: imported: 9 skipped: 41 backfilled: 0 errors: 0 sessions_seen: 50 sessions_tagged: 50 created_at: type: string format: date-time description: ISO-8601 creation timestamp. example: '2026-07-20T09:15:00.000Z' updated_at: type: string format: date-time description: ISO-8601 timestamp of the last edit. example: '2026-07-22T18:41:55.117Z' RemoteSourceCreateRequest: type: object description: Request body to register a remote SSH source. `label` and `host` are required; the rest are optional. No credentials are ever accepted or stored — auth defers to the host SSH stack. required: - label - host properties: label: type: string description: Human-readable name. example: Work laptop host: type: string description: SSH destination (`user@host`) or a `~/.ssh/config` alias. example: son@studio.local ssh_port: type: integer description: Optional SSH port. example: 22 identity_file: type: string description: Optional private-key path passed to ssh (`-i`). example: ~/.ssh/id_ed25519 remote_home: type: string description: Optional remote Claude home; defaults to the remote `~/.claude`. example: ~/.claude enabled: type: boolean description: Whether the source is enabled for syncing (default true). example: true RemoteSourceUpdateRequest: type: object description: Partial update for a remote source. Only the keys present in the body are changed; omitted keys are left as-is. Same field set as create; both `label` and `host` are optional here. properties: label: type: string example: Studio Mac host: type: string example: son@studio.local ssh_port: type: integer nullable: true example: 2222 identity_file: type: string nullable: true example: ~/.ssh/id_ed25519 remote_home: type: string nullable: true example: ~/.claude enabled: type: boolean example: false RemoteSourceResponse: type: object required: - source properties: source: $ref: '#/components/schemas/RemoteSource' RemoteSourceListResponse: type: object required: - sources properties: sources: type: array items: $ref: '#/components/schemas/RemoteSource' RemoteSourceTestResponse: type: object description: Result of an SSH connectivity probe. required: - ok - message properties: ok: type: boolean example: true message: type: string description: Human-readable probe result. example: Connected; found 24 project directories under ~/.claude/projects. remoteProjects: type: array description: Optional list of remote project directories discovered during the probe (present on success). items: type: string example: - '-Users-son-code-foo' - '-Users-son-code-bar' RemoteSourceSyncResponse: type: object description: Counters from a pull-now sync against the remote source. required: - ok - imported - skipped - backfilled - errors - sessions_seen - sessions_tagged properties: ok: type: boolean example: true imported: type: integer example: 9 skipped: type: integer example: 41 backfilled: type: integer example: 0 errors: type: integer example: 0 sessions_seen: type: integer example: 50 sessions_tagged: type: integer description: Number of imported sessions stamped with this source's id. example: 50 SettingsClaudeHomeResponse: type: object description: The Claude Code home directory the dashboard reads transcripts and settings from. Defaults to `~/.claude` unless overridden via the CLAUDE_HOME environment variable. required: - claude_home properties: claude_home: type: string description: Absolute path to the active Claude Code home directory (CLAUDE_HOME, or `/.claude` when unset). example: /Users/son/.claude SettingsClaudeHomeUpdateRequest: type: object description: Request body for changing the Claude Code home directory. A leading `~` is expanded to the user's home directory; the resolved path must be absolute and point to an existing directory. required: - path properties: path: type: string description: New Claude Code home directory. A leading `~/` is expanded to the OS home directory before validation. Must resolve to an absolute path that exists and is a directory. example: ~/.codefuse/engine/cc SettingsClaudeHomeUpdateResponse: type: object description: Confirmation that CLAUDE_HOME was updated. The new value is applied to process.env immediately and persisted to the project `.env` file. required: - ok - claude_home properties: ok: type: boolean enum: - true claude_home: type: string description: The resolved absolute path now in effect (after `~` expansion). example: /Users/son/.codefuse/engine/cc WorkflowToolRun: type: object description: 'A Claude Code Workflow-tool run (issue #167): a fleet of sub-agents spawned by the ''Workflow'' tool (or self-paced /loop). These emit no hooks; the source of truth is the on-disk run journal, ingested into the `workflows` table (see server/lib/workflow-ingest.js). Keyed by `run_id` and parented to the launching session. The JSON-blob columns `phases` and `progress` are parsed into arrays before serialization.' required: - run_id - session_id - status - agent_count - total_tokens - total_tool_calls - phases - progress - source - created_at - updated_at properties: run_id: type: string description: Primary key — the workflow run id. example: wf_a1b2c3d4 session_id: type: string description: The session that launched this run (FK into sessions.id). example: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 task_id: type: string nullable: true description: Optional task/issue identifier associated with the run. example: ISSUE-167 name: type: string nullable: true description: Human-readable run name from the journal, if present. example: Refactor pricing engine status: type: string description: Open status string (e.g. running | completed | error | failed). Intentionally not constrained to an enum so new harness states never trip a stale constraint. example: completed default_model: type: string nullable: true description: Default model the run delegated work to, when recorded. example: claude-opus-4-8 started_at: type: string format: date-time nullable: true description: When the run started, if known. example: '2026-06-25T18:04:11.122Z' ended_at: type: string format: date-time nullable: true description: When the run finished, if known. example: '2026-06-25T18:09:47.530Z' duration_ms: type: integer nullable: true description: Total run duration in milliseconds, if known. example: 336408 agent_count: type: integer minimum: 0 description: Number of inner agents in this run. example: 6 total_tokens: type: integer minimum: 0 description: Aggregate token usage across the run's inner agents. example: 1284750 total_tool_calls: type: integer minimum: 0 description: Aggregate tool-call count across the run's inner agents. example: 412 phases: type: array description: Parsed `phases[]` array from the run journal (verbatim journal payload, opaque to this API). Empty array when absent or unparseable. items: type: object additionalProperties: true example: - name: plan status: completed - name: implement status: completed progress: type: array description: Parsed `workflowProgress[]` array from the run journal (verbatim journal payload, opaque to this API). Empty array when absent or unparseable. items: type: object additionalProperties: true example: - step: 1 label: scaffold done: true script_path: type: string nullable: true description: Path to the run's driving script, if recorded. example: /Users/son/.claude/projects/-Users-son-code/wf_a1b2c3d4.sh journal_path: type: string nullable: true description: Path to the on-disk run journal this row was ingested from. example: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json source: type: string description: Ingestion source for the row (defaults to 'journal'). example: journal created_at: type: string format: date-time description: Row creation timestamp. example: '2026-06-25T18:09:48.001Z' updated_at: type: string format: date-time description: Row last-update timestamp. example: '2026-06-25T18:09:48.001Z' WorkflowRunsListResponse: type: object description: Paginated list of Workflow-tool runs with status counts. `total` reflects the active filter (status when supplied, otherwise the full table); `counts` is always the whole-table breakdown by status. required: - runs - total - counts - limit - offset properties: runs: type: array items: $ref: '#/components/schemas/WorkflowToolRun' total: type: integer minimum: 0 description: 'Total runs matching the current filter (independent of limit/offset). Equals the status-filtered count when `status` is supplied, otherwise the full-table count. Note: not narrowed by `session_id`.' example: 42 counts: type: object description: Whole-table run counts grouped by status (not affected by filters). additionalProperties: type: integer minimum: 0 example: completed: 30 error: 5 running: 7 limit: type: integer description: Effective page size used. example: 50 offset: type: integer description: Effective pagination offset used. example: 0 WorkflowRunDetailResponse: type: object description: A single Workflow-tool run with its linked inner agents and the events attributed to those agents (chronological, capped at 5000). required: - workflow - agents - events properties: workflow: $ref: '#/components/schemas/WorkflowToolRun' agents: type: array description: Inner agents linked to this run via agents.workflow_run_id. items: $ref: '#/components/schemas/Agent' events: type: array description: Events attributed to this run's inner agents, ordered by created_at then id. Capped at 5000 rows. items: $ref: '#/components/schemas/DashboardEvent' paths: /api/health: get: tags: - Health summary: Health check operationId: getHealth responses: '200': description: Service is healthy content: application/json: schema: $ref: '#/components/schemas/HealthResponse' /api/sessions: get: tags: - Sessions summary: List sessions description: Returns a paginated list of sessions, newest activity first, each enriched with a SQL `agent_count` (LEFT JOIN onto agents), a `last_activity` alias of `updated_at`, and a `cost` computed from the session's token usage against the current pricing rules. The `status` and `q` filters compose (AND) with each other and with pagination; `q` is a case-insensitive LIKE across `id`, `name`, and `cwd`. `total` reflects all rows matching the filters independent of `limit`/`offset` so paginators stay accurate, while `cost` is only calculated for the rows on the returned page (when `sort_by=price` it is computed across all matching rows so the price sort is correct). The endpoint is read-only with no side effects; `metadata` on each session is returned as a raw JSON-encoded string, not a parsed object. operationId: listSessions parameters: - $ref: '#/components/parameters/SessionStatusQuery' example: active - name: q in: query schema: type: string description: Case-insensitive search across `id` / `name` / `cwd`. Composes with the status filter when both are present. example: pricing - $ref: '#/components/parameters/SourcesQuery' example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 - $ref: '#/components/parameters/LimitQuery' example: 50 - $ref: '#/components/parameters/OffsetQuery' example: 0 responses: '200': description: Session list content: application/json: schema: $ref: '#/components/schemas/SessionsListResponse' example: sessions: - id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint status: active cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 started_at: '2026-06-25T14:02:11.004Z' ended_at: null metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}' updated_at: '2026-06-25T14:31:50.119Z' agent_count: 4 last_activity: '2026-06-25T14:31:50.119Z' cost: 0.8421 awaiting_input_since: null awaiting_reason: null - id: 1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9 name: Fix flaky transcript pagination test status: completed cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-sonnet-4-20250514 started_at: '2026-06-24T09:12:00.000Z' ended_at: '2026-06-24T09:48:32.501Z' metadata: null updated_at: '2026-06-24T09:48:32.501Z' agent_count: 1 last_activity: '2026-06-24T09:48:32.501Z' cost: 0.1532 awaiting_input_since: null awaiting_reason: null limit: 50 offset: 0 total: 137 post: tags: - Sessions summary: Create session (idempotent) description: 'Creates a session keyed by `id`. The operation is idempotent: if a session with that `id` already exists it is returned untouched with `created: false` and HTTP 200; only a brand-new row yields `created: true` and HTTP 201. New sessions are inserted with `status: "active"` and any omitted optional fields stored as null. The `metadata` field is accepted as a JSON object in the request but persisted (and returned on the session) as a JSON-encoded string. A successful create broadcasts a `session_created` websocket frame. A missing `id` returns 400 with code `INVALID_INPUT`.' operationId: createSession requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SessionCreateRequest' example: id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 metadata: source: hook git_branch: feat/spend-budgets responses: '200': description: Session already exists content: application/json: schema: $ref: '#/components/schemas/SessionCreateResponse' example: session: id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint status: active cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 started_at: '2026-06-25T14:02:11.004Z' ended_at: null metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}' updated_at: '2026-06-25T14:31:50.119Z' agent_count: 4 last_activity: '2026-06-25T14:31:50.119Z' cost: 0.8421 awaiting_input_since: null awaiting_reason: null created: false '201': description: Session created content: application/json: schema: $ref: '#/components/schemas/SessionCreateResponse' example: session: id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint status: active cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 started_at: '2026-06-25T14:02:11.004Z' ended_at: null metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}' updated_at: '2026-06-25T14:02:11.004Z' agent_count: 0 last_activity: '2026-06-25T14:02:11.004Z' cost: 0 awaiting_input_since: null awaiting_reason: null created: true '400': description: Invalid request body content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_INPUT message: id is required /api/sessions/{id}: get: tags: - Sessions summary: Get session details description: Returns a single session together with all of its agents (chronological) and persisted events. Read-only, no side effects. The session's `metadata` and each event's `data` are returned as raw JSON-encoded strings, not parsed objects. Returns 404 with code `NOT_FOUND` when no session matches the path `id`. operationId: getSession parameters: - $ref: '#/components/parameters/SessionIdPath' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d responses: '200': description: Session with associated agents/events content: application/json: schema: $ref: '#/components/schemas/SessionDetailResponse' example: session: id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint status: active cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 started_at: '2026-06-25T14:02:11.004Z' ended_at: null metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}' updated_at: '2026-06-25T14:31:50.119Z' agent_count: 4 last_activity: '2026-06-25T14:31:50.119Z' cost: 0.8421 awaiting_input_since: null awaiting_reason: null agents: - id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Main Agent type: main subagent_type: null status: working task: null current_tool: Edit started_at: '2026-06-25T14:02:11.004Z' ended_at: null parent_agent_id: null metadata: '{"model":"claude-opus-4-20250514"}' updated_at: '2026-06-25T14:31:50.119Z' awaiting_input_since: null awaiting_reason: null - id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: completed task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: '2026-06-25T14:14:09.882Z' parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: null updated_at: '2026-06-25T14:14:09.882Z' awaiting_input_since: null awaiting_reason: null events: - id: 48213 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main event_type: PostToolUse tool_name: Edit summary: Edited server/routes/pricing.js data: '{"tool_input":{"file_path":"server/routes/pricing.js"},"tool_response":{"success":true}}' created_at: '2026-06-25T14:31:50.119Z' '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Session not found patch: tags: - Sessions summary: Update session description: Partially updates a session by `id`. Only `name`, `status`, `ended_at`, and `metadata` are accepted; any field omitted from the body is passed as null and the underlying UPDATE uses COALESCE, so a null leaves the existing column value unchanged (partial-update semantics) — you cannot clear a field to null through this endpoint. `metadata` is supplied as a JSON object but stored and returned as a JSON-encoded string. A successful update re-reads the row and broadcasts a `session_updated` websocket frame. Returns 404 with code `NOT_FOUND` when the session does not exist. operationId: updateSession parameters: - $ref: '#/components/parameters/SessionIdPath' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SessionUpdateRequest' example: status: completed ended_at: '2026-06-25T15:07:44.220Z' metadata: source: hook git_branch: feat/spend-budgets outcome: merged responses: '200': description: Session updated content: application/json: schema: $ref: '#/components/schemas/SessionUpdateResponse' example: session: id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Refactor pricing route + add cost endpoint status: completed cwd: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor model: claude-opus-4-20250514 started_at: '2026-06-25T14:02:11.004Z' ended_at: '2026-06-25T15:07:44.220Z' metadata: '{"source":"hook","git_branch":"feat/spend-budgets","outcome":"merged"}' updated_at: '2026-06-25T15:07:44.220Z' agent_count: 4 last_activity: '2026-06-25T15:07:44.220Z' cost: 0.8421 awaiting_input_since: null awaiting_reason: null '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Session not found /api/sessions/{id}/stats: get: tags: - Sessions summary: Get aggregated session stats description: 'Returns aggregated counts for the SessionOverview panel: total events, events-by-type, the top 15 tools by usage, an error count (events whose `event_type`/`summary` match /error/i or /failed/i), the event time range, agent type/status counts, the subagent-type breakdown (excluding the special `compaction` type, which is surfaced under `agents.compaction`), and token totals. All aggregation runs in SQL, so it stays cheap even for sessions with tens of thousands of events; the endpoint is read-only with no side effects. The frontend debounces calls on `new_event` / `agent_*` / `session_updated` websocket frames so the counters track a running session. Returns 404 with code `NOT_FOUND` when the session does not exist.' operationId: getSessionStats parameters: - $ref: '#/components/parameters/SessionIdPath' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d responses: '200': description: Aggregated session stats content: application/json: schema: $ref: '#/components/schemas/SessionStatsResponse' example: session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d total_events: 1284 events_by_type: - event_type: PostToolUse count: 612 - event_type: PreToolUse count: 612 - event_type: Notification count: 41 - event_type: Stop count: 19 tools_used: - tool_name: Bash count: 188 - tool_name: Edit count: 143 - tool_name: Read count: 121 - tool_name: Grep count: 77 error_count: 6 first_event_at: '2026-06-25T14:02:11.052Z' last_event_at: '2026-06-25T14:31:50.119Z' agents: total: 4 main: 1 subagent: 3 compaction: 1 by_status: working: 1 completed: 2 error: 1 subagent_types: - subagent_type: Explore count: 2 - subagent_type: general-purpose count: 1 tokens: input_tokens: 18422 output_tokens: 9134 cache_read_tokens: 1204880 cache_write_tokens: 88210 '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Session not found /api/sessions/{id}/transcripts: get: tags: - Sessions summary: List available transcripts for a session description: Lists every JSONL transcript file associated with a session — the main agent's transcript plus any subagent and compaction transcripts — by scanning the on-disk Claude project directory (live files, falling back to import-time snapshots). Read-only, no side effects. Each entry carries a best-effort `db_agent_id` resolved by matching transcripts to tracked agents (exact id first, then positional-by-time within each type group); it may be null when a transcript has no matching agent row. Used by the Conversation tab to populate the transcript switcher. Returns 404 with code `NOT_FOUND` when the session does not exist. operationId: listSessionTranscripts parameters: - $ref: '#/components/parameters/SessionIdPath' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d responses: '200': description: List of transcripts available for the session content: application/json: schema: $ref: '#/components/schemas/TranscriptListResponse' example: transcripts: - id: main name: Main Agent type: main has_transcript: true db_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main - id: ad18a79192af10ed1 name: Explore pricing module type: subagent subagent_type: Explore has_transcript: true db_agent_id: ad18a79192af10ed1 - id: acompact-7c1e2f90 name: Context Compaction type: compaction subagent_type: null has_transcript: true db_agent_id: null '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Session not found /api/sessions/{id}/transcript: get: tags: - Sessions summary: Stream messages from a specific transcript description: 'Returns parsed, renderable messages from a JSONL transcript with cursor-based pagination, reading the live file under ~/.claude/projects and falling back to the durable import-time snapshot. Pass `agent_id` to select a specific subagent or compaction transcript (default is the session''s main transcript). Pagination cursors are mutually exclusive: `after` returns messages strictly newer than a JSONL line number (incremental live updates on `new_event`), `before` returns messages strictly older than a line (load-on-scroll-up), and `offset` is legacy start-offset paging. `last_line`/`first_line` are the JSONL line numbers of the newest/oldest returned message — feed them back as `after`/`before`. When the session, transcript file, or path cannot be found the endpoint degrades gracefully to an empty result (`messages: []`, `total: 0`, `has_more: false`) rather than erroring. Read-only, no side effects.' operationId: getSessionTranscript parameters: - $ref: '#/components/parameters/SessionIdPath' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d - name: agent_id in: query schema: type: string description: Transcript identifier — 'main' for the session's main transcript, or a subagent / compaction id from /transcripts. example: main - name: limit in: query schema: type: integer default: 50 minimum: 1 maximum: 500 description: Maximum number of messages to return. example: 50 - name: offset in: query schema: type: integer minimum: 0 description: Offset from the start of the transcript (mutually exclusive with after/before). example: 0 - name: after in: query schema: type: integer minimum: 0 description: Only return messages whose JSONL line number is strictly greater than this value. Used for incremental live updates. example: 842 - name: before in: query schema: type: integer minimum: 0 description: Only return messages whose JSONL line number is strictly less than this value. Used to load older messages on scroll-up. example: 200 responses: '200': description: Parsed messages with cursor metadata content: application/json: schema: $ref: '#/components/schemas/TranscriptResponse' example: messages: - type: user timestamp: '2026-06-25T14:02:11.004Z' content: - type: text text: Refactor the pricing route and add a cost endpoint. - type: assistant timestamp: '2026-06-25T14:02:18.771Z' model: claude-opus-4-20250514 content: - type: thinking text: I'll start by reading server/routes/pricing.js. - type: tool_use name: Read id: toolu_01A7c2Df9 input: file_path: server/routes/pricing.js usage: input_tokens: 412 output_tokens: 96 - type: user timestamp: '2026-06-25T14:02:19.330Z' content: - type: tool_result id: toolu_01A7c2Df9 output: |- const { Router } = require("express"); ... is_error: false total: 1284 has_more: true last_line: 5310 first_line: 5301 '404': description: Session or transcript not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Session not found /api/agents: get: tags: - Agents summary: List agents description: 'Returns agents, most recent first. Filters are applied with precedence rather than composition: when `session_id` is supplied it wins and returns every agent for that session (ignoring `status` and pagination); otherwise a `status` filter returns paginated agents in that lifecycle state; otherwise all agents are returned paginated. `limit` defaults to 10000 when not a positive integer. Read-only, no side effects. Each agent''s `metadata` is returned as a raw JSON-encoded string, not a parsed object.' operationId: listAgents parameters: - $ref: '#/components/parameters/AgentStatusQuery' example: working - $ref: '#/components/parameters/SessionFilterQuery' example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d - $ref: '#/components/parameters/SourcesQuery' example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 - $ref: '#/components/parameters/LimitQuery' example: 50 - $ref: '#/components/parameters/OffsetQuery' example: 0 responses: '200': description: Agent list content: application/json: schema: $ref: '#/components/schemas/AgentsListResponse' example: agents: - id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Main Agent type: main subagent_type: null status: working task: null current_tool: Edit started_at: '2026-06-25T14:02:11.004Z' ended_at: null parent_agent_id: null metadata: '{"model":"claude-opus-4-20250514"}' updated_at: '2026-06-25T14:31:50.119Z' awaiting_input_since: null awaiting_reason: null - id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: completed task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: '2026-06-25T14:14:09.882Z' parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: null updated_at: '2026-06-25T14:14:09.882Z' awaiting_input_since: null awaiting_reason: null limit: 50 offset: 0 post: tags: - Agents summary: Create agent (idempotent) description: 'Creates an agent keyed by `id`. The operation is idempotent: if an agent with that `id` already exists it is returned untouched with `created: false` and HTTP 200; only a brand-new row yields `created: true` and HTTP 201. Omitted optional fields default server-side — `type` to `"main"`, `status` to `"waiting"` — and other unspecified columns are stored as null. `metadata` is accepted as a JSON object but persisted (and returned) as a JSON-encoded string. A successful create broadcasts an `agent_created` websocket frame. Missing `id`, `session_id`, or `name` returns 400 with code `INVALID_INPUT`.' operationId: createAgent requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentCreateRequest' example: id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: working task: Map every caller of calculateCost() across server/routes parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: spawned_by: Task responses: '200': description: Agent already exists content: application/json: schema: $ref: '#/components/schemas/AgentCreateResponse' example: agent: id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: completed task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: '2026-06-25T14:14:09.882Z' parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: null updated_at: '2026-06-25T14:14:09.882Z' awaiting_input_since: null awaiting_reason: null created: false '201': description: Agent created content: application/json: schema: $ref: '#/components/schemas/AgentCreateResponse' example: agent: id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: working task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: null parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: '{"spawned_by":"Task"}' updated_at: '2026-06-25T14:10:22.310Z' awaiting_input_since: null awaiting_reason: null created: true '400': description: Invalid request body content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_INPUT message: id, session_id, and name are required /api/agents/{id}: get: tags: - Agents summary: Get agent description: Returns a single agent by `id`. Read-only, no side effects. The agent's `metadata` is returned as a raw JSON-encoded string, not a parsed object. Returns 404 with code `NOT_FOUND` when no agent matches the path `id`. operationId: getAgent parameters: - $ref: '#/components/parameters/AgentIdPath' example: ad18a79192af10ed1 responses: '200': description: Agent details content: application/json: schema: $ref: '#/components/schemas/AgentDetailResponse' example: agent: id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: completed task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: '2026-06-25T14:14:09.882Z' parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: null updated_at: '2026-06-25T14:14:09.882Z' awaiting_input_since: null awaiting_reason: null '404': description: Agent not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Agent not found patch: tags: - Agents summary: Update agent description: 'Partially updates an agent by `id`. Accepts `name`, `status`, `task`, `current_tool`, `ended_at`, and `metadata`. The UPDATE uses COALESCE, so any field omitted (passed as null) leaves the existing column value unchanged — with one deliberate exception: `current_tool` is written through verbatim when present in the body, so it can be explicitly cleared to null (e.g. when a tool call finishes). `metadata` is supplied as a JSON object but stored and returned as a JSON-encoded string. A successful update re-reads the row and broadcasts an `agent_updated` websocket frame. Returns 404 with code `NOT_FOUND` when the agent does not exist.' operationId: updateAgent parameters: - $ref: '#/components/parameters/AgentIdPath' example: ad18a79192af10ed1 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentUpdateRequest' example: status: completed current_tool: null ended_at: '2026-06-25T14:14:09.882Z' responses: '200': description: Agent updated content: application/json: schema: $ref: '#/components/schemas/AgentUpdateResponse' example: agent: id: ad18a79192af10ed1 session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d name: Explore pricing module type: subagent subagent_type: Explore status: completed task: Map every caller of calculateCost() across server/routes current_tool: null started_at: '2026-06-25T14:10:22.310Z' ended_at: '2026-06-25T14:14:09.882Z' parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main metadata: null updated_at: '2026-06-25T14:14:09.882Z' awaiting_input_since: null awaiting_reason: null '404': description: Agent not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Agent not found /api/events: get: tags: - Events summary: List events with multi-dimensional filtering operationId: listEvents description: |- Returns a paginated, reverse-chronological slice of the `events` table (ordered by `created_at DESC, id DESC`) together with the total row count matching the active filters, so the UI can drive a paginator without a second request. All four entity filters — `event_type`, `tool_name`, `agent_id`, and `session_id` — accept a **comma-separated list (CSV)** of values and match with `IN (...)` semantics: passing `event_type=Stop,PreToolUse` returns rows whose `event_type` is either `Stop` OR `PreToolUse`. Values are trimmed and blank entries are dropped. Filters are combined with one another using AND. `q` performs a case-insensitive substring (`LIKE %q%`) search across the `summary`, `tool_name`, and the JSON-encoded `data` columns. `from`/`to` are inclusive ISO-8601 datetime bounds on `created_at`; unparseable values are ignored rather than rejected. `limit` is clamped to 1–500 (default 50) and `offset` is clamped to >= 0 (default 0). Note: each returned event's `data` field is a **JSON-encoded string**, not a nested object — callers must `JSON.parse` it to inspect the payload. parameters: - in: query name: event_type description: 'Comma-separated (CSV) list of `event_type` values; matched with IN semantics (OR within the list). Common values: PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd.' schema: type: string example: Stop,PreToolUse - in: query name: tool_name description: 'Comma-separated (CSV) list of `tool_name` values; matched with IN semantics (OR within the list). Common values: Bash, Edit, Read, Write, Grep, Glob, Task.' schema: type: string example: Bash,Edit - in: query name: agent_id description: Comma-separated (CSV) list of `agent_id` values; matched with IN semantics. The main agent of a session uses the id `-main`. schema: type: string example: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main - in: query name: session_id description: Comma-separated (CSV) list of `session_id` values; matched with IN semantics (OR within the list). schema: type: string example: 8f3c2a10-1b2c-4d5e-9f80-112233445566,2a7d9e44-3c1f-4a6b-bc20-aabbccddeeff - in: query name: q description: Case-insensitive substring search (`LIKE %q%`) applied across the `summary`, `tool_name`, and JSON-encoded `data` columns. schema: type: string example: curl - in: query name: from description: ISO-8601 datetime lower bound (inclusive) on `created_at`. Unparseable values are ignored. schema: type: string format: date-time example: '2026-06-25T00:00:00.000Z' - in: query name: to description: ISO-8601 datetime upper bound (inclusive) on `created_at`. Unparseable values are ignored. schema: type: string format: date-time example: '2026-06-26T00:00:00.000Z' - $ref: '#/components/parameters/SourcesQuery' example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 - in: query name: limit description: Max rows to return; clamped to 1–500 (default 50). schema: type: integer minimum: 1 maximum: 500 default: 50 example: 50 - $ref: '#/components/parameters/OffsetQuery' responses: '200': description: Event list with total count for pagination content: application/json: schema: $ref: '#/components/schemas/EventsListResponse' example: events: - id: 48213 session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566 agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main event_type: PreToolUse tool_name: Bash summary: 'Bash: curl -s https://api.example.com/health' data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"curl -s https://api.example.com/health","description":"Check upstream health"},"cwd":"/Users/dev/project"}' created_at: '2026-06-25T18:42:07.512Z' - id: 48212 session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566 agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main event_type: Stop tool_name: null summary: Session finished responding data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"Stop"}' created_at: '2026-06-25T18:41:55.004Z' limit: 50 offset: 0 total: 1342 /api/events/facets: get: tags: - Events summary: Distinct event_type and tool_name values available in the DB operationId: listEventFacets description: Returns the distinct, non-null `event_type` and `tool_name` values currently present in the `events` table, each sorted alphabetically. The UI uses this to populate the filter dropdowns on the Events screen without hardcoding the set of tools or hook types — so the lists automatically reflect whatever has actually been ingested. Both arrays are independent and may be empty when the table holds no matching rows. responses: '200': description: Facet values for populating filter dropdowns content: application/json: schema: $ref: '#/components/schemas/EventsFacetsResponse' example: event_types: - Notification - PostToolUse - PreToolUse - SessionEnd - SessionStart - Stop - SubagentStop tool_names: - Bash - Edit - Glob - Grep - Read - Task - Write /api/stats: get: tags: - Stats summary: Get aggregate dashboard stats operationId: getStats description: |- Returns the headline counters shown across the top of the dashboard: total and active session/agent counts, total event count, today's event count, and the current number of live WebSocket connections. The overview counters are spread at the top level of the response object. Two additional maps, `agents_by_status` and `sessions_by_status`, break the counts down by lifecycle status (e.g. agents: working/waiting/completed/error; sessions: active/completed/error/abandoned). **Statuses with a zero count are omitted from these maps**, so callers must not assume every status key is present. `events_today` is computed in the caller's local day. Pass `tz_offset` as the minutes value from JavaScript's `Date.prototype.getTimezoneOffset()` (for example `420` for US Pacific Daylight Time, `300` for US Eastern Daylight Time, `0` for UTC). When omitted or non-numeric, the server falls back to UTC (offset 0). parameters: - in: query name: tz_offset description: Caller timezone offset in MINUTES, as returned by JS `Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for UTC). Used to bucket `events_today` into the caller's local day. Defaults to 0 (UTC) when omitted or non-numeric. schema: type: integer example: 420 - $ref: '#/components/parameters/SourcesQuery' example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 responses: '200': description: Statistics overview content: application/json: schema: $ref: '#/components/schemas/StatsResponse' example: total_sessions: 184 active_sessions: 3 active_agents: 5 total_agents: 372 total_events: 28451 events_today: 612 ws_connections: 2 agents_by_status: working: 4 waiting: 1 completed: 360 error: 7 sessions_by_status: active: 3 completed: 175 error: 6 /api/metrics: get: tags: - Metrics summary: Prometheus metrics exposition operationId: getMetrics description: Exposes the dashboard's live counters (sessions/agents by status, event and token totals, connected realtime clients, configured remote sources, process uptime/memory, build version) in the Prometheus text-exposition format (v0.0.4) for scraping into Prometheus / Grafana. Read-only. Mounted under `/api`, so it honors the optional `DASHBOARD_TOKEN` guard — point the scrape job's bearer token at it when the server is bound to a LAN. responses: '200': description: Prometheus text exposition (v0.0.4). content: text/plain: schema: type: string example: | # HELP ccam_up 1 when the dashboard API is serving this scrape. # TYPE ccam_up gauge ccam_up 1 # HELP ccam_sessions Number of sessions by lifecycle status. # TYPE ccam_sessions gauge ccam_sessions{status="active"} 3 /api/analytics: get: tags: - Analytics summary: Get analytics aggregates operationId: getAnalytics description: |- Returns the full analytics rollup powering the Analytics screen: aggregate token usage (`tokens`), total estimated spend across all sessions (`total_cost`, in USD, computed from the configured pricing rules), per-tool invocation counts (`tool_usage`), per-day event and session time series (`daily_events`, `daily_sessions`), the distribution of subagent types (`agent_types`), per-type event counts (`event_types`), the mean number of events per session (`avg_events_per_session`), the total subagent count (`total_subagents`), and a nested `overview` object mirroring the headline session/agent/event counters. As with `/api/stats`, the top-level `agents_by_status` and `sessions_by_status` maps **omit statuses whose count is zero**. The `agent_types[].subagent_type` field may be `null` for the main agent / untyped subagents. The daily time series are bucketed by the caller's local day. Pass `tz_offset` as the minutes value from JS `Date.prototype.getTimezoneOffset()` (e.g. `420` for PDT). When omitted or non-numeric, the server buckets in UTC. parameters: - in: query name: tz_offset description: Caller timezone offset in MINUTES, as returned by JS `Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for UTC). Used to bucket the `daily_events` / `daily_sessions` time series into the caller's local day. Defaults to UTC when omitted or non-numeric. schema: type: integer example: 420 - $ref: '#/components/parameters/SourcesQuery' example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 responses: '200': description: Analytics response content: application/json: schema: $ref: '#/components/schemas/AnalyticsResponse' example: tokens: total_input: 4821002 total_output: 1933517 total_cache_read: 19288440 total_cache_write: 2044120 total_cost: 42.7183 tool_usage: - tool_name: Bash count: 5821 - tool_name: Read count: 4310 - tool_name: Edit count: 2980 - tool_name: Grep count: 1744 daily_events: - date: '2026-06-23' count: 488 - date: '2026-06-24' count: 921 - date: '2026-06-25' count: 612 daily_sessions: - date: '2026-06-23' count: 4 - date: '2026-06-24' count: 9 - date: '2026-06-25' count: 6 agent_types: - subagent_type: null count: 184 - subagent_type: general-purpose count: 96 - subagent_type: Explore count: 71 - subagent_type: code-reviewer count: 21 event_types: - event_type: PreToolUse count: 14210 - event_type: PostToolUse count: 13988 - event_type: Stop count: 168 - event_type: SubagentStop count: 85 avg_events_per_session: 154.6 total_subagents: 188 overview: total_sessions: 184 active_sessions: 3 active_agents: 5 total_agents: 372 total_events: 28451 agents_by_status: working: 4 waiting: 1 completed: 360 error: 7 sessions_by_status: active: 3 completed: 175 error: 6 /api/hooks/event: post: tags: - Hooks summary: Ingest Claude Code hook event operationId: ingestHookEvent description: |- Primary ingestion endpoint for Claude Code lifecycle hooks. The hook handler posts an envelope of the form `{ hook_type, data }`, where `hook_type` is the Claude Code hook name (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd) and `data` carries the raw hook payload — at minimum a `session_id`. The server upserts the session and its main agent on first sight, applies the appropriate lifecycle state transition, extracts token usage and compaction signals from the transcript when present, persists an `events` row (storing `data` as a JSON-encoded string), and broadcasts a `new_event` message over the WebSocket. On success the response is `{ ok: true, event: { ... } }`, where `event` echoes the normalized row that was just inserted (`session_id`, `agent_id`, `event_type`, `tool_name`, `summary`, `created_at`). Ingestion is designed to be fail-safe and non-blocking for the hook caller. Validation failures return HTTP 400 with an `ErrorResponse` body (`{ error: { code, message } }`): `INVALID_INPUT` when `hook_type` or `data` is missing, and `MISSING_SESSION` when `data.session_id` is absent. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HookEventRequest' example: hook_type: PreToolUse data: session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566 hook_event_name: PreToolUse tool_name: Bash tool_input: command: curl -s https://api.example.com/health description: Check upstream health cwd: /Users/dev/project transcript_path: /Users/dev/.claude/projects/-Users-dev-project/8f3c2a10-1b2c-4d5e-9f80-112233445566.jsonl responses: '200': description: Event processed content: application/json: schema: $ref: '#/components/schemas/HookEventResponse' example: ok: true event: session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566 agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main event_type: PreToolUse tool_name: Bash summary: 'Bash: curl -s https://api.example.com/health' created_at: '2026-06-25T18:42:07.512Z' '400': description: Invalid hook payload content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: MISSING_SESSION message: session_id is required in data /api/pricing: get: tags: - Pricing summary: List pricing rules operationId: listPricingRules description: 'Returns every stored pricing rule, wrapped as `{ pricing: [ ... ] }`. Each rule carries per-MTok (per-million-token) rates for input, output, cache reads, and the two cache-write tiers (5-minute and 1-hour ephemeral), plus optional fast-mode input/output rates (0 = not configured). Rules are matched against model ids by treating the SQL `%` wildcard in `model_pattern` as `.*`; when several rules match, the longest (most specific) pattern wins. Rates here feed the cost calculations under `/api/pricing/cost`.' responses: '200': description: Pricing rules content: application/json: schema: $ref: '#/components/schemas/PricingListResponse' example: pricing: - model_pattern: claude-opus-4% display_name: Claude Opus 4 (family) input_per_mtok: 15 output_per_mtok: 75 cache_read_per_mtok: 1.5 cache_write_per_mtok: 18.75 cache_write_1h_per_mtok: 30 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-25T18:42:11.000Z' - model_pattern: claude-haiku% display_name: Claude Haiku (family) input_per_mtok: 0.8 output_per_mtok: 4 cache_read_per_mtok: 0.08 cache_write_per_mtok: 1 cache_write_1h_per_mtok: 1.6 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-20T09:15:00.000Z' put: tags: - Pricing summary: Create/update pricing rule operationId: upsertPricingRule description: 'Creates a pricing rule or updates the existing one with the same `model_pattern` (upsert keyed on `model_pattern`). `model_pattern` and `display_name` are required; every `*_per_mtok` rate is optional and defaults to 0 when omitted. Use the SQL `%` wildcard in `model_pattern` to match a model family (e.g. `claude-opus-4%`). Set `fast_input_per_mtok` / `fast_output_per_mtok` only if the model bills fast-mode usage at a premium; leave them 0 otherwise. Note the asymmetry with the list endpoint: the response wraps a SINGLE stored rule as `{ pricing: }` (not an array). A missing `model_pattern` or `display_name` returns 400 `INVALID_INPUT`, and so does any `*_per_mtok` rate that is not a non-negative finite number (numeric strings are coerced; NaN and negative rates are rejected before anything is written).' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PricingUpsertRequest' example: model_pattern: claude-opus-4% display_name: Claude Opus 4 (family) input_per_mtok: 15 output_per_mtok: 75 cache_read_per_mtok: 1.5 cache_write_per_mtok: 18.75 cache_write_1h_per_mtok: 30 fast_input_per_mtok: 0 fast_output_per_mtok: 0 responses: '200': description: Pricing rule stored content: application/json: schema: $ref: '#/components/schemas/PricingUpsertResponse' example: pricing: model_pattern: claude-opus-4% display_name: Claude Opus 4 (family) input_per_mtok: 15 output_per_mtok: 75 cache_read_per_mtok: 1.5 cache_write_per_mtok: 18.75 cache_write_1h_per_mtok: 30 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-25T18:42:11.000Z' '400': description: Invalid request body content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_INPUT message: model_pattern and display_name are required /api/pricing/{pattern}: delete: tags: - Pricing summary: Delete pricing rule operationId: deletePricingRule description: 'Deletes the pricing rule whose `model_pattern` exactly matches the `pattern` path segment. The pattern is URL-ENCODED: the SQL `%` wildcard must be sent as `%25` (so `claude-opus-4%` becomes `claude-opus-4%25`). The server decodes it before lookup. Returns `{ ok: true }` on success, or 404 `NOT_FOUND` if no rule matches.' parameters: - name: pattern in: path required: true schema: type: string description: Model pattern (URL-encoded). The SQL `%` wildcard must be escaped as `%25` (e.g. `claude-opus-4%25` for the rule `claude-opus-4%`). example: claude-opus-4%25 responses: '200': description: Rule deleted content: application/json: schema: $ref: '#/components/schemas/DeleteOkResponse' example: ok: true '404': description: Pricing rule not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Pricing rule not found /api/pricing/cost: get: tags: - Pricing summary: Get total token cost across all sessions operationId: getTotalCost description: Computes the aggregate token cost across EVERY session by matching each (model, speed, inference_geo, service_tier) usage bucket against the most specific pricing rule. Returns `total_cost`, a per-bucket `breakdown`, `feature_costs` (web-search surcharge, code-execution container time with the org free-hours allowance applied), `unpriced_models` (usage with no matching rule, contributing $0 so the total stays honest), and `daily_costs` bucketed by local calendar day. Pass `tz_offset` (minutes; the JS `Date.getTimezoneOffset()` value, e.g. 300 for US Eastern, -120 for CEST) so day boundaries align with the viewer's timezone; omitted/invalid offsets fall back to UTC. Honors the `sources` data-scope filter, like the sessions / stats / analytics endpoints, so the reported cost matches the active scope. parameters: - name: tz_offset in: query required: false schema: type: integer description: Viewer timezone offset in minutes, as returned by `Date.getTimezoneOffset()` (positive for zones behind UTC, e.g. 300 = US Eastern, -120 = CEST). Shifts the `daily_costs` day boundaries; invalid or omitted values default to UTC. example: 300 - name: sources in: query required: false schema: type: string description: Comma-separated data-source ids to include (local history is `local`; remote SSH machines use their `remote_sources.id`). Omit for all sources. Narrows the aggregate cost to the given origins. example: local responses: '200': description: Cost result content: application/json: schema: $ref: '#/components/schemas/CostResult' example: total_cost: 12.8431 breakdown: - model: claude-opus-4-8 speed: standard inference_geo: global service_tier: standard input_tokens: 184320 output_tokens: 51200 cache_read_tokens: 920000 cache_write_tokens: 64000 cache_write_1h_tokens: 12000 web_search_requests: 8 web_fetch_requests: 3 code_execution_requests: 2 cost: 8.4127 matched_rule: claude-opus-4% - model: claude-haiku-4-5 speed: fast inference_geo: us service_tier: standard input_tokens: 512000 output_tokens: 128000 cache_read_tokens: 64000 cache_write_tokens: 8000 cache_write_1h_tokens: 0 web_search_requests: 0 web_fetch_requests: 0 code_execution_requests: 0 cost: 1.5904 matched_rule: claude-haiku% feature_costs: web_search_cost: 0.08 web_fetch_cost: 0 code_execution_cost: 0 code_execution_hours_estimated: 0.1667 code_execution_free_hours: 50 unpriced_models: - model: claude-experimental-preview input_tokens: 4096 output_tokens: 2048 cache_read_tokens: 0 cache_write_tokens: 0 daily_costs: - date: '2026-06-23' cost: 3.1102 - date: '2026-06-24' cost: 5.7421 - date: '2026-06-25' cost: 3.9908 /api/pricing/cost/{sessionId}: get: tags: - Pricing summary: Get token cost for one session operationId: getSessionCost description: Same cost computation as `/api/pricing/cost`, but scoped to a single session's token usage. Returns the identical `CostResult` shape (`total_cost`, `breakdown`, `feature_costs`, `unpriced_models`, `daily_costs`); `daily_costs` holds at most one entry — the session's start date in the viewer's local day, or an empty array if the session id is unknown. Pass `tz_offset` (minutes, `Date.getTimezoneOffset()`) to place that start date in the viewer's timezone; defaults to UTC. parameters: - name: sessionId in: path required: true schema: type: string description: Session ID to price. example: sess_8f2a3b1c - name: tz_offset in: query required: false schema: type: integer description: Viewer timezone offset in minutes (`Date.getTimezoneOffset()`; 300 = US Eastern, -120 = CEST). Places the session start date in the viewer's local day; defaults to UTC when omitted or invalid. example: 300 responses: '200': description: Session cost result content: application/json: schema: $ref: '#/components/schemas/CostResult' example: total_cost: 8.4127 breakdown: - model: claude-opus-4-8 speed: standard inference_geo: global service_tier: standard input_tokens: 184320 output_tokens: 51200 cache_read_tokens: 920000 cache_write_tokens: 64000 cache_write_1h_tokens: 12000 web_search_requests: 8 web_fetch_requests: 3 code_execution_requests: 2 cost: 8.4127 matched_rule: claude-opus-4% - model: claude-haiku-4-5 speed: fast inference_geo: us service_tier: standard input_tokens: 512000 output_tokens: 128000 cache_read_tokens: 64000 cache_write_tokens: 8000 cache_write_1h_tokens: 0 web_search_requests: 0 web_fetch_requests: 0 code_execution_requests: 0 cost: 1.5904 matched_rule: claude-haiku% feature_costs: web_search_cost: 0.08 web_fetch_cost: 0 code_execution_cost: 0 code_execution_hours_estimated: 0.1667 code_execution_free_hours: 50 unpriced_models: - model: claude-experimental-preview input_tokens: 4096 output_tokens: 2048 cache_read_tokens: 0 cache_write_tokens: 0 daily_costs: - date: '2026-06-25' cost: 8.4127 /api/workflows: get: tags: - Workflows summary: Get workflow intelligence aggregates description: 'Returns the full workflow-intelligence aggregate powering the Workflows analytics page — 11 sections in one payload: `stats` (headline counters: sessions, agents, subagents, success rate, avg depth/duration, compactions, top tool flow), `orchestration` (subagent-type breakdown + delegation edges + outcomes), `toolFlow` (tool-to-tool transitions + tool counts), `effectiveness` (per-subagent-type success rate, avg duration, weekly trend), `patterns` (frequent subagent sequences + solo-session share), `modelDelegation` (model usage for main/sub agents + tokens by model), `errorPropagation` (errors by depth/type + error rate), `concurrency` (averaged agent swim-lane start/end), `complexity` (per-session agent/token/duration rows), `compaction` (compaction counts + tokens recovered), and `cooccurrence` (directed subagent-after-subagent pairs). The optional `status` query filter scopes every section to sessions of one status. Errors use the SHORT `{ error: { message } }` shape.' operationId: getWorkflowIntelligence parameters: - $ref: '#/components/parameters/WorkflowStatusQuery' responses: '200': description: Workflow aggregate data content: application/json: schema: $ref: '#/components/schemas/WorkflowAggregateResponse' example: stats: totalSessions: 412 totalAgents: 1893 totalSubagents: 1481 avgSubagents: 3.6 successRate: 94.2 avgDepth: 1.4 avgDurationSec: 1187 totalCompactions: 96 avgCompactions: 0.2 topFlow: source: Read target: Edit count: 3120 orchestration: sessionCount: 412 mainCount: 412 subagentTypes: - subagent_type: general-purpose count: 612 completed: 590 errors: 8 - subagent_type: Explore count: 388 completed: 380 errors: 2 edges: - source: main target: general-purpose weight: 612 outcomes: - status: completed count: 1402 - status: error count: 79 compactions: total: 96 sessions: 71 toolFlow: transitions: - source: Read target: Edit value: 3120 toolCounts: - tool_name: Read count: 9821 - tool_name: Edit count: 5402 - tool_name: Bash count: 4810 effectiveness: - subagent_type: general-purpose total: 612 completed: 590 errors: 8 sessions: 281 successRate: 98.7 avgDuration: 142 trend: - 88 - 91 - 102 - 97 - 110 - 24 - 12 patterns: patterns: - steps: - Explore - general-purpose count: 142 percentage: 34.5 soloSessionCount: 120 soloPercentage: 29.1 modelDelegation: mainModels: - model: claude-opus-4-8 agent_count: 290 session_count: 290 subagentModels: - model: claude-opus-4-8 agent_count: 980 tokensByModel: - model: claude-opus-4-8 input_tokens: 4820112 output_tokens: 1920334 cache_read_tokens: 88201442 cache_write_tokens: 12044210 errorPropagation: byDepth: - depth: 0 count: 41 - depth: 1 count: 38 byType: - subagent_type: general-purpose count: 8 eventErrors: - summary: 'Error in Bash: command not found' count: 5 sessionsWithErrors: 33 totalSessions: 412 errorRate: 8 concurrency: aggregateLanes: - name: Main Agent avgStart: 0 avgEnd: 1 count: 412 - name: Explore avgStart: 0.12 avgEnd: 0.31 count: 388 complexity: - id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 name: Refactor auth flow status: completed duration: 2538 agentCount: 6 subagentCount: 5 totalTokens: 940212 model: claude-opus-4-8 compaction: totalCompactions: 96 tokensRecovered: 18402991 perSession: - session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 compactions: 3 sessionsWithCompactions: 71 totalSessions: 412 cooccurrence: - source: Explore target: general-purpose weight: 142 '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: 'no such table: agents' /api/workflows/session/{id}: get: tags: - Workflows summary: Get workflow drill-in for one session description: 'Returns the workflow drill-in for a single session, used by the session-level Workflow view: `session` (the session row), `tree` (the recursive parent→child agent tree rooted at the main agent), `toolTimeline` (chronological tool events with tool_name/event_type/agent_id/summary), `swimLanes` (a flat per-agent start/end lane list for the Gantt-style view), and `events` (the chronological event stream, capped at the first 500 rows). Returns 404 with the SHORT `{ error: { message } }` shape when the session id is unknown.' operationId: getWorkflowSession parameters: - $ref: '#/components/parameters/SessionIdPath' responses: '200': description: Workflow session detail content: application/json: schema: $ref: '#/components/schemas/WorkflowSessionResponse' example: session: id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 name: Refactor auth flow status: completed cwd: /Users/son/code/foo model: claude-opus-4-8 started_at: '2026-06-25T13:00:00.000Z' ended_at: '2026-06-25T13:42:18.220Z' metadata: null updated_at: '2026-06-25T13:42:18.220Z' tree: - id: agent_main_5f3c0e2a name: Main agent type: main subagent_type: null status: completed task: null started_at: '2026-06-25T13:00:00.000Z' ended_at: '2026-06-25T13:42:18.220Z' children: - id: agent_sub_a1 name: Explore the auth module type: subagent subagent_type: Explore status: completed task: Map auth call sites started_at: '2026-06-25T13:02:10.000Z' ended_at: '2026-06-25T13:06:55.000Z' children: [] toolTimeline: - id: 91021 tool_name: Read event_type: PreToolUse agent_id: agent_main_5f3c0e2a created_at: '2026-06-25T13:01:02.500Z' summary: Read server/auth.js swimLanes: - id: agent_main_5f3c0e2a name: Main agent type: main subagent_type: null status: completed started_at: '2026-06-25T13:00:00.000Z' ended_at: '2026-06-25T13:42:18.220Z' parent_agent_id: null events: - id: 91021 session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 agent_id: agent_main_5f3c0e2a event_type: PreToolUse tool_name: Read summary: Read server/auth.js data: null created_at: '2026-06-25T13:01:02.500Z' '404': description: Session not found content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: Session not found '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: 'no such table: agents' /api/settings/info: get: tags: - Settings summary: Get system/database/hook diagnostics description: 'Returns a diagnostics snapshot used by the Settings page: `db` (database file path, on-disk size in bytes, per-table row counts, SQLite pragmas, and recent event load over the last 5/15/60 minutes), `hooks` (whether the Claude Code hook-handler is installed in `settings.json`, the settings path, and a per-hook-type installed map), `server` (process uptime, Node version, platform, live WebSocket connection count, memory/CPU/host stats), and `transcript_cache` (number of cached transcript entries and the cached paths). Read-only and cheap to poll.' operationId: getSettingsInfo responses: '200': description: Settings and diagnostics content: application/json: schema: $ref: '#/components/schemas/SettingsInfoResponse' example: db: path: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor/server/data/agent-monitor.db size: 48922624 counts: sessions: 412 agents: 1893 events: 58117 model_pricing: 11 token_usage: 401 pragmas: journal_mode: wal synchronous: 1 auto_vacuum: 0 encoding: UTF-8 foreign_keys: 1 busy_timeout: 5000 load_stats: m5: 23 m15: 88 h1: 511 hooks: installed: true path: /Users/son/.claude/settings.json hooks: PreToolUse: true PostToolUse: true Stop: true SubagentStop: true Notification: true SessionStart: true SessionEnd: true server: uptime: 14523.91 node_version: v22.14.0 platform: darwin ws_connections: 2 memory: rss: 142802944 heapTotal: 71303168 heapUsed: 58392104 external: 3211884 arrayBuffers: 1048576 cpu_load: - 2.13 - 2.45 - 2.31 arch: arm64 total_mem: 17179869184 free_mem: 2147483648 cpus: 10 transcript_cache: entries: 37 paths: - /Users/son/.claude/projects/-Users-son-code-foo/abc123.jsonl - /Users/son/.claude/projects/-Users-son-code-bar/def456.jsonl /api/settings/clear-data: post: tags: - Settings summary: Delete all dashboard data description: '⚠ DESTRUCTIVE — IRREVERSIBLE. Deletes ALL sessions, agents, events, token_usage rows, the fired-alert feed (alert_events), and the webhook delivery log. There is no confirmation step and no undo — export first via GET /api/settings/export if you need a backup. User CONFIGURATION survives: alert *rules*, webhook *targets*, and model_pricing are preserved (they''re settings, not captured data). The response echoes the row counts that existed BEFORE the wipe so the UI can report what was removed.' operationId: clearData responses: '200': description: Data cleared content: application/json: schema: $ref: '#/components/schemas/ClearDataResponse' example: ok: true cleared: sessions: 412 agents: 1893 events: 58117 model_pricing: 11 token_usage: 401 /api/settings/reimport: post: tags: - Settings summary: Re-import legacy sessions from ~/.claude description: Re-runs the legacy history importer against the default `~/.claude` projects directory, funneling every transcript through the same parser + importSession pipeline the live server uses. This is IDEMPOTENT and ADDITIVE — already-imported sessions are deduplicated (counted under `skipped`), token counts and compaction baselines are preserved so cost never double-counts, and nothing existing is deleted. The response reports how many sessions were `imported` vs `skipped`, plus an `errors` count for transcripts that failed to parse. operationId: reimportLegacySessions responses: '200': description: Import completed content: application/json: schema: $ref: '#/components/schemas/ReimportResponse' example: ok: true imported: 38 skipped: 374 errors: 0 '500': description: Import failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: IMPORT_FAILED message: 'ENOENT: no such file or directory, scandir ''/Users/son/.claude/projects''' /api/settings/reinstall-hooks: post: tags: - Settings summary: Reinstall Claude Code hooks description: 'Re-runs the hook installer to (re)wire the dashboard''s hook-handler into Claude Code''s `settings.json` for all seven hook types (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd). Safe to re-run — it overwrites/repairs the dashboard''s own hook entries without touching unrelated user hooks. The response returns the post-install hook status so the UI can confirm every hook type is now `installed: true`.' operationId: reinstallHooks responses: '200': description: Hooks reinstall result content: application/json: schema: $ref: '#/components/schemas/ReinstallHooksResponse' example: ok: true hooks: installed: true path: /Users/son/.claude/settings.json hooks: PreToolUse: true PostToolUse: true Stop: true SubagentStop: true Notification: true SessionStart: true SessionEnd: true '500': description: Hook installation failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: HOOK_INSTALL_FAILED message: 'EACCES: permission denied, open ''/Users/son/.claude/settings.json''' /api/settings/reset-pricing: post: tags: - Settings summary: Reset pricing table to defaults description: ⚠ DESTRUCTIVE to pricing customizations. Deletes EVERY row in the model_pricing table and re-seeds it from the dashboard's built-in DEFAULT_PRICING list. Any custom rates or custom model patterns you added are permanently lost — there is no undo. Captured session/token data is untouched (only the pricing rules used to *compute* cost change). The response returns the full freshly-seeded pricing table. operationId: resetPricing responses: '200': description: Pricing defaults restored content: application/json: schema: $ref: '#/components/schemas/ResetPricingResponse' example: ok: true pricing: - model_pattern: claude-opus-4* display_name: Claude Opus 4 input_per_mtok: 15 output_per_mtok: 75 cache_read_per_mtok: 1.5 cache_write_per_mtok: 18.75 cache_write_1h_per_mtok: 30 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-26T00:00:00.000Z' - model_pattern: claude-sonnet-4* display_name: Claude Sonnet 4 input_per_mtok: 3 output_per_mtok: 15 cache_read_per_mtok: 0.3 cache_write_per_mtok: 3.75 cache_write_1h_per_mtok: 6 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-26T00:00:00.000Z' /api/settings/export: get: tags: - Settings summary: Export all dashboard data as JSON description: 'Exports the entire dataset as a single versioned JSON document — all sessions, agents, events, token_usage rows, workflows, dashboard_runs, alert_rules, and model_pricing — stamped with `format: "ccam-export"`, `version`, and `exported_at`. Served with a `Content-Disposition: attachment` header (filename `agent-monitor-export-YYYY-MM-DD.json`) so browsers download it. Use it to back up before a destructive operation (clear-data / cleanup with purge_days) or to migrate/consolidate data across machines — the bundle is re-importable via POST /api/settings/import. Read-only; nothing is modified.' operationId: exportData responses: '200': description: Export payload (served as attachment) content: application/json: schema: $ref: '#/components/schemas/ExportResponse' example: format: ccam-export version: 1 exported_at: '2026-06-26T01:12:44.913Z' sessions: - id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 name: Refactor auth flow status: completed cwd: /Users/son/code/foo model: claude-opus-4-8 started_at: '2026-06-25T13:00:00.000Z' ended_at: '2026-06-25T13:42:18.220Z' metadata: null updated_at: '2026-06-25T13:42:18.220Z' agents: - id: agent_main_5f3c0e2a session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 name: Main agent type: main subagent_type: null status: completed task: null current_tool: null started_at: '2026-06-25T13:00:00.000Z' ended_at: '2026-06-25T13:42:18.220Z' parent_agent_id: null metadata: null updated_at: '2026-06-25T13:42:18.220Z' events: - id: 91021 session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 agent_id: agent_main_5f3c0e2a event_type: PreToolUse tool_name: Edit summary: Edit server/auth.js data: null created_at: '2026-06-25T13:05:42.001Z' token_usage: - session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 model: claude-opus-4-8 input_tokens: 18422 output_tokens: 9120 cache_read_tokens: 220184 cache_write_tokens: 41002 model_pricing: - model_pattern: claude-opus-4* display_name: Claude Opus 4 input_per_mtok: 15 output_per_mtok: 75 cache_read_per_mtok: 1.5 cache_write_per_mtok: 18.75 cache_write_1h_per_mtok: 30 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-26T00:00:00.000Z' - model_pattern: claude-sonnet-4* display_name: Claude Sonnet 4 input_per_mtok: 3 output_per_mtok: 15 cache_read_per_mtok: 0.3 cache_write_per_mtok: 3.75 cache_write_1h_per_mtok: 6 fast_input_per_mtok: 0 fast_output_per_mtok: 0 updated_at: '2026-06-26T00:00:00.000Z' /api/settings/cleanup: post: tags: - Settings summary: Abandon stale sessions and optionally purge old history description: 'Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. The response reports counts for each phase.' operationId: cleanupData requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CleanupRequest' examples: tidyOnly: summary: 'Safe: abandon stale sessions only (no deletion)' value: abandon_hours: 12 abandonAndPurge: summary: Abandon stale sessions, then purge history older than 90 days (destructive) value: abandon_hours: 12 purge_days: 90 responses: '200': description: Cleanup result content: application/json: schema: $ref: '#/components/schemas/CleanupResponse' example: ok: true abandoned: 3 purged_sessions: 57 purged_events: 14820 purged_agents: 241 /api/import/guide: get: tags: - Import summary: Import guide with OS-aware defaults and step-by-step instructions description: 'Returns the OS-aware import guide the Import page renders verbatim: the detected `platform`, the default `~/.claude/projects` directory (raw + display form), whether it exists and how many projects/JSONL files it holds, an OS-specific `archive_command` for bundling history off another machine, the supported file extensions, the upload size/count limits, and four ordered `steps` (locate → archive → choose mode → verify). Read-only; performs no import.' operationId: importGuide responses: '200': description: Guide payload content: application/json: schema: $ref: '#/components/schemas/ImportGuideResponse' example: platform: darwin default_projects_dir: /Users/son/.claude/projects default_projects_dir_display: ~/.claude/projects default_projects_dir_exists: true default_projects_dir_stats: projects: 24 jsonl_files: 312 archive_command: tar -czf claude-history.tar.gz -C ~/.claude projects supported_extensions: - .jsonl - .meta.json - .zip - .tar - .tar.gz - .tgz - .gz max_upload_bytes: 1073741824 max_upload_files: 2000 steps: - id: locate title: Locate your Claude Code history body: Claude Code stores every session as a JSONL transcript under ~/.claude/projects. Each subdirectory is named after the working directory where the session started (with slashes replaced by dashes). - id: archive title: Bundle it for transfer (optional) body: |- If you're importing from another machine, archive the whole projects folder first: tar -czf claude-history.tar.gz -C ~/.claude projects Move claude-history.tar.gz to this machine however you like (AirDrop, scp, USB, cloud storage). - id: choose title: Pick an import mode body: 'Rescan default: re-read ~/.claude/projects on this machine and import anything new. From folder: point the dashboard at any directory you''ve extracted history into. Upload: drag-drop JSONL files or an archive directly into the browser.' - id: verify title: Verify tokens and cost body: 'Imports are idempotent: re-running is always safe. Token counts are deduplicated per session ID, with compaction baselines preserved so cost never double-counts. After import, open Analytics → Cost to confirm the breakdown.' /api/import/rescan: post: tags: - Import summary: Rescan the default ~/.claude/projects directory description: 'Re-scans the default `~/.claude/projects` directory and imports anything new through the live ingestion pipeline. IDEMPOTENT and ADDITIVE — re-running is always safe, already-imported sessions are deduplicated (`skipped`), and token/compaction baselines are preserved so cost never double-counts. Progress is broadcast over the WebSocket as `import.progress` frames while it runs. The response reports `imported` / `skipped` / `backfilled` / `errors` plus `sessions_seen` and `files_scanned`, with `source: "default"`.' operationId: importRescan responses: '200': description: Import result content: application/json: schema: $ref: '#/components/schemas/ImportResultResponse' example: ok: true source: default imported: 14 skipped: 298 backfilled: 2 errors: 0 sessions_seen: 312 files_scanned: 312 '500': description: Import failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: IMPORT_FAILED message: Failed to read projects directory /api/import/scan-path: post: tags: - Import summary: Import transcripts from an arbitrary absolute directory description: 'Imports transcripts from an arbitrary directory you point the dashboard at (e.g. history extracted from another machine). The `path` must resolve to an existing directory: a leading `~` is expanded to the home directory, the result must be absolute, and subdirectories are walked recursively for `.jsonl` files. Same idempotent, baseline-preserving pipeline as the default rescan; progress is broadcast as `import.progress`. The response echoes the resolved `path` and the per-run counters with `source: "path"`.' operationId: importScanPath requestBody: required: true content: application/json: schema: type: object required: - path properties: path: type: string description: Absolute directory path. Tilde (~) is expanded. Walks subdirectories recursively. examples: absolute: summary: Absolute directory value: path: /Users/son/Downloads/claude-history/projects tilde: summary: Tilde expanded to home directory value: path: ~/Downloads/claude-history/projects responses: '200': description: Import result content: application/json: schema: $ref: '#/components/schemas/ImportResultResponse' example: ok: true source: path path: /Users/son/Downloads/claude-history/projects imported: 9 skipped: 41 backfilled: 0 errors: 0 sessions_seen: 50 files_scanned: 50 '400': description: Path validation failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: missing: summary: path omitted value: error: code: INVALID_INPUT message: '`path` is required' notAbsolute: summary: path is not absolute value: error: code: INVALID_INPUT message: '`path` must be an absolute path' notFound: summary: path does not exist value: error: code: PATH_NOT_FOUND message: 'Path does not exist: /tmp/nope' notDir: summary: path is a file, not a directory value: error: code: NOT_A_DIRECTORY message: 'Path is not a directory: /tmp/foo.jsonl' '500': description: Import failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: IMPORT_FAILED message: Unexpected end of JSON input /api/import/upload: post: tags: - Import summary: Upload JSONL files or archives (.zip, .tar, .tar.gz, .tgz, .gz) description: 'Imports history uploaded directly from the browser as `multipart/form-data` under the `files` field. Accepts raw `.jsonl` / `.meta.json` transcripts and/or archives (`.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`), which are extracted into a temp dir and walked for JSONL content. Unsupported extensions are silently rejected and reported in `rejected_files`. Extraction is bounded to defend against zip bombs — exceeding the limit returns 413. Same idempotent import pipeline; progress is broadcast as `import.progress`. On success the response carries `source: "upload"` plus `files_received`, `rejected_files`, `entries_extracted`, `entries_skipped`, and the standard import counters. (Requires the optional `multer` dependency; a missing install yields a 500.)' operationId: importUpload requestBody: required: true content: multipart/form-data: schema: type: object properties: files: type: array items: type: string format: binary description: Files to import. Supports .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz. example: files: - claude-history.tar.gz - session-extra.jsonl responses: '200': description: Import result content: application/json: schema: $ref: '#/components/schemas/ImportResultResponse' example: ok: true source: upload files_received: 3 rejected_files: - notes.txt entries_extracted: 128 entries_skipped: 2 imported: 12 skipped: 116 backfilled: 0 errors: 0 sessions_seen: 128 files_scanned: 128 '400': description: No files or no JSONL content content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: noFiles: summary: No supported files in the upload value: error: code: NO_FILES message: No supported files in upload. 1 file(s) rejected (unsupported extension). noJsonl: summary: Archive extracted but contained no .jsonl value: error: code: NO_JSONL message: 'No .jsonl files were found in the uploaded content. Supported inputs: .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.' '413': description: Extraction limit exceeded (possible zip bomb) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EXTRACTION_LIMIT_EXCEEDED message: Archive exceeded the extraction size limit (possible zip bomb). '500': description: Upload or import failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: noMulter: summary: Optional multer dependency not installed value: error: code: UPLOADER_UNAVAILABLE message: File upload requires `multer`. Run `npm install` to pick up new deps. importFailed: summary: Import pipeline error value: error: code: IMPORT_FAILED message: Unexpected end of JSON input /api/updates/status: get: tags: - Updates summary: Check whether the dashboard git checkout is behind origin description: 'Reports whether the dashboard''s own git checkout is behind its upstream remote, so a user can pull and restart manually (the dashboard never self-restarts). The response is a variant object: when it IS a git checkout it includes `git_repo: true`, `repo_root`, `update_available`, `commits_behind`, `remote_ref`, `local_sha`, `remote_sha`, and a copy-pasteable `manual_command`; when it is NOT a git checkout (e.g. an npm/tarball install) it returns `git_repo: false` with a `message` and no diff fields. Read-only — this only inspects git, it does not fetch destructively or modify the working tree.' operationId: getUpdatesStatus responses: '200': description: Update check result content: application/json: schema: type: object additionalProperties: true description: Includes git_repo, update_available, commits_behind, remote_ref, local_sha, remote_sha, manual_command, and optional error/message fields. examples: updateAvailable: summary: Behind origin — update available value: git_repo: true repo_root: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor update_available: true commits_behind: 4 remote_ref: origin/master local_sha: de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7 remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 manual_command: npm run self-update checked_at: '2026-06-26T01:20:09.553Z' upToDate: summary: Up to date with origin value: git_repo: true repo_root: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor update_available: false commits_behind: 0 remote_ref: origin/master local_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 manual_command: npm run self-update checked_at: '2026-06-26T01:25:41.002Z' notARepo: summary: Not a git checkout — detection unavailable value: git_repo: false update_available: false message: Not a git checkout — update detection is unavailable for this install. '500': description: Update status query failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: UPDATE_STATUS_FAILED message: 'git: command not found' /api/updates/check: post: tags: - Updates summary: Run an update check immediately and broadcast the result description: Runs the same upstream check as GET /api/updates/status immediately and, in addition to returning the result, broadcasts it to every connected client as an `update_status` WebSocket message — so all open dashboard tabs refresh their update banner at once. The response shape is identical to GET /api/updates/status (the same git_repo / update_available / commits_behind / manual_command variant object). Read-only with respect to the working tree. operationId: triggerUpdatesCheck responses: '200': description: Fresh update status payload (also broadcast over WebSocket) content: application/json: schema: type: object additionalProperties: true description: Same shape as GET /api/updates/status. Also sent as an update_status WebSocket message to all connected clients. examples: updateAvailable: summary: Behind origin — update available value: git_repo: true repo_root: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor update_available: true commits_behind: 4 remote_ref: origin/master local_sha: de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7 remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 manual_command: npm run self-update checked_at: '2026-06-26T01:20:09.553Z' upToDate: summary: Up to date with origin value: git_repo: true repo_root: /Users/son/WebstormProjects/Claude-Code-Agent-Monitor update_available: false commits_behind: 0 remote_ref: origin/master local_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 manual_command: npm run self-update checked_at: '2026-06-26T01:25:41.002Z' '500': description: Update check failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: UPDATE_CHECK_FAILED message: 'git fetch failed: network unreachable' /api/alerts: get: tags: - Alerts summary: List fired alerts, newest first operationId: listAlerts description: Returns the fired-alert feed, newest first, as `{ alerts, total, unacked, limit, offset }`. Each alert event carries the originating rule's id/name/type, a human-readable `message`, an `acknowledged` flag (0/1), `created_at`, and `details` — which is a JSON STRING (not an object) that callers must `JSON.parse`. `limit` is clamped to 1–200 (default 50) and negative `offset` is clamped to 0. Set `unacked=true` to return only unacknowledged alerts; `total` then counts only unacked rows, while `unacked` always reports the global unacknowledged count. parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 200 default: 50 description: Page size, clamped to the 1–200 range (default 50). Values outside the range are clamped, not rejected. example: 50 - name: offset in: query required: false schema: type: integer minimum: 0 description: Pagination offset; negative values are clamped to 0. example: 0 - name: unacked in: query required: false schema: type: boolean description: When the literal string "true", return only unacknowledged alerts (and scope `total` to that subset). example: 'true' responses: '200': description: Paginated alert feed with total and unacked counts content: application/json: schema: type: object additionalProperties: true description: Includes alerts[], total, unacked, limit, offset. example: alerts: - id: 42 rule_id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 rule_name: Idle session watchdog rule_type: inactivity message: Session sess_8f2a has been inactive for 35 minutes details: '{"session_id":"sess_8f2a","minutes":35,"threshold":30}' acknowledged: 0 created_at: '2026-06-25T17:05:44.000Z' - id: 41 rule_id: 1a2b3c4d-5e6f-7081-9201-aabbccddeeff rule_name: Heavy token burn rule_type: token_threshold message: Session sess_3c1d crossed 5,000,000 total tokens details: '{"session_id":"sess_3c1d","total_tokens":5120000,"threshold":5000000}' acknowledged: 1 created_at: '2026-06-25T16:40:02.000Z' total: 2 unacked: 1 limit: 50 offset: 0 /api/alerts/rules: get: tags: - Alerts summary: List alert rules operationId: listAlertRules description: 'Returns all alert rules as `{ rules: [ ... ] }`. Each rule''s `config` is returned as a PARSED object (the column is stored as JSON text), and `enabled` is coerced to a boolean. The `config` shape depends on `rule_type`: `event_pattern` uses event_type / tool_name / summary_contains plus optional count + window_minutes; `inactivity` uses `minutes`; `status_duration` uses `status` + `minutes`; `token_threshold` uses `total_tokens`.' responses: '200': description: All alert rules with parsed config objects content: application/json: schema: type: object additionalProperties: true example: rules: - id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 name: Idle session watchdog rule_type: inactivity config: minutes: 30 enabled: true cooldown_seconds: 300 created_at: '2026-06-10T12:00:00.000Z' updated_at: '2026-06-24T08:30:00.000Z' - id: 1a2b3c4d-5e6f-7081-9201-aabbccddeeff name: Heavy token burn rule_type: token_threshold config: total_tokens: 5000000 enabled: true cooldown_seconds: 600 created_at: '2026-06-12T14:20:00.000Z' updated_at: '2026-06-12T14:20:00.000Z' post: tags: - Alerts summary: Create an alert rule operationId: createAlertRule description: |- Creates an alert rule and returns it serialized as `{ rule: { ... } }` with HTTP 201. `name`, `rule_type`, and `config` are required; the `config` shape is validated per `rule_type`: - `event_pattern`: `{ event_type?, tool_name?, summary_contains?, count?, window_minutes? }` — fires when matching events accumulate. - `inactivity`: `{ minutes }` — fires when a session goes idle. - `status_duration`: `{ status, minutes }` — fires when a session holds a status too long. - `token_threshold`: `{ total_tokens }` — fires when usage crosses a ceiling. `enabled` defaults to true and `cooldown_seconds` defaults to 300 (must be a non-negative integer). A bad name, unknown `rule_type`, invalid `config`, or negative cooldown returns 400 `INVALID_INPUT`. requestBody: required: true content: application/json: schema: type: object required: - name - rule_type - config properties: name: type: string rule_type: type: string enum: - event_pattern - inactivity - status_duration - token_threshold config: type: object additionalProperties: true description: 'Type-specific config. event_pattern: event_type/tool_name/summary_contains + optional count/window_minutes. inactivity: minutes. status_duration: status + minutes. token_threshold: total_tokens.' enabled: type: boolean default: true cooldown_seconds: type: integer default: 300 examples: inactivity: summary: Inactivity rule value: name: Idle session watchdog rule_type: inactivity config: minutes: 30 enabled: true cooldown_seconds: 300 event_pattern: summary: Event-pattern rule (repeated tool errors) value: name: Repeated Bash failures rule_type: event_pattern config: event_type: PostToolUse tool_name: Bash summary_contains: error count: 3 window_minutes: 10 enabled: true cooldown_seconds: 600 status_duration: summary: Status-duration rule value: name: Stuck waiting too long rule_type: status_duration config: status: waiting minutes: 15 enabled: true cooldown_seconds: 300 token_threshold: summary: Token-threshold rule value: name: Heavy token burn rule_type: token_threshold config: total_tokens: 5000000 enabled: true cooldown_seconds: 600 responses: '201': description: Created rule content: application/json: schema: type: object additionalProperties: true example: rule: id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 name: Idle session watchdog rule_type: inactivity config: minutes: 30 enabled: true cooldown_seconds: 300 created_at: '2026-06-10T12:00:00.000Z' updated_at: '2026-06-24T08:30:00.000Z' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_INPUT message: name is required /api/alerts/rules/{id}: patch: tags: - Alerts summary: Update an alert rule (partial; rule_type is immutable) operationId: updateAlertRule description: 'Partially updates an alert rule and returns it serialized as `{ rule: { ... } }`. Only the fields present in the body change; `rule_type` CANNOT be changed and any supplied `config` is validated against the rule''s STORED type. `name` (if present) must be a non-empty string and `cooldown_seconds` (if present) must be a non-negative integer. Returns 404 `NOT_FOUND` for an unknown id, or 400 `INVALID_INPUT` for a bad name, invalid config, or negative cooldown.' parameters: - name: id in: path required: true schema: type: string description: Alert rule ID (UUID). example: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 requestBody: required: true content: application/json: schema: type: object properties: name: type: string config: type: object additionalProperties: true enabled: type: boolean cooldown_seconds: type: integer examples: disableRule: summary: Disable a rule without touching its config value: enabled: false retuneInactivity: summary: Re-tune an inactivity rule's threshold + cooldown value: config: minutes: 45 cooldown_seconds: 900 rename: summary: Rename a rule value: name: Idle session watchdog (prod) responses: '200': description: Updated rule content: application/json: schema: type: object additionalProperties: true example: rule: id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 name: Idle session watchdog rule_type: inactivity config: minutes: 30 enabled: false cooldown_seconds: 900 created_at: '2026-06-10T12:00:00.000Z' updated_at: '2026-06-24T08:30:00.000Z' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_INPUT message: cooldown_seconds must be a non-negative integer '404': description: Rule not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Alert rule not found delete: tags: - Alerts summary: Delete an alert rule and its fired-alert history operationId: deleteAlertRule description: 'Deletes the alert rule with the given id. Its fired-alert history cascades away with it (the foreign key is ON DELETE CASCADE), so any alerts previously raised by this rule are also removed from the feed. Returns `{ ok: true }` on success or 404 `NOT_FOUND` for an unknown id.' parameters: - name: id in: path required: true schema: type: string description: Alert rule ID (UUID). example: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 responses: '200': description: Deletion confirmation content: application/json: schema: type: object additionalProperties: true example: ok: true '404': description: Rule not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Alert rule not found /api/alerts/{id}/ack: post: tags: - Alerts summary: Acknowledge one fired alert operationId: ackAlert description: 'Marks a single fired alert (by its integer event id) as acknowledged and returns the updated row as `{ alert: { ... } }` (with `acknowledged: 1`). Acknowledging also broadcasts an `alert_updated` WebSocket message so connected dashboards refresh their unacked badge. The id must be numeric; an unknown id returns 404 `NOT_FOUND`.' parameters: - name: id in: path required: true schema: type: integer description: Alert event ID (numeric). example: 42 responses: '200': description: Acknowledged alert row content: application/json: schema: type: object additionalProperties: true example: alert: id: 42 rule_id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91 rule_name: Idle session watchdog rule_type: inactivity message: Session sess_8f2a has been inactive for 35 minutes details: '{"session_id":"sess_8f2a","minutes":35,"threshold":30}' acknowledged: 1 created_at: '2026-06-25T17:05:44.000Z' '404': description: Alert not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Alert not found /api/alerts/ack-all: post: tags: - Alerts summary: Acknowledge all unacked alerts operationId: ackAllAlerts description: 'Acknowledges every currently unacknowledged alert in one call and returns `{ ok: true, acknowledged: }` where `acknowledged` is the number of rows actually updated. When at least one alert is acknowledged, an `alert_updated` WebSocket message (`{ acked_all: true }`) is broadcast so dashboards clear their unacked badge. Calling this when nothing is unacked returns `acknowledged: 0`.' responses: '200': description: Count of acknowledged alerts content: application/json: schema: type: object additionalProperties: true example: ok: true acknowledged: 3 /api/webhooks/providers: get: tags: - Webhooks summary: List supported providers + their config fields (for the UI) description: 'Returns the redacted provider catalog the webhook-target editor renders. For each of the 14 supported providers (Slack, Discord, Teams, Google Chat, Mattermost, Rocket.Chat, Telegram, PagerDuty, Opsgenie, Splunk On-Call, Zapier, Make, n8n, Pipedream) plus the `generic` family, it lists: a human label, the provider family, whether the URL must be https / is user-supplied, and the per-provider config field definitions (key, label, type, required, options, and whether the field is secret). No secret values are ever included — this is purely the *shape* of the form, not stored credentials.' operationId: listWebhookProviders responses: '200': description: 'Provider catalog: label, family, url requirements, fields' content: application/json: schema: type: object additionalProperties: true example: providers: slack: label: Slack family: chat https: true urlRequired: true fields: [] telegram: label: Telegram family: chat https: true urlRequired: false fields: - key: bot_token label: Bot token type: string required: true secret: true - key: chat_id label: Chat ID type: string required: true secret: false pagerduty: label: PagerDuty family: incident https: true urlRequired: false fields: - key: routing_key label: Routing key type: string required: true secret: true - key: severity label: Severity type: enum required: false options: - critical - error - warning - info secret: false generic: label: Generic webhook family: generic https: false urlRequired: true fields: [] /api/webhooks: get: tags: - Webhooks summary: List webhook targets (URLs masked, secrets redacted) description: 'Lists every configured webhook target. Responses are always redacted: the full URL is never returned — only `url_preview` (protocol + host + a `…` + the last 4 chars), `has_secret` is a boolean (never the secret itself), and any secret-flagged provider config fields (routing keys, API keys, bot tokens) and custom header values are masked to `••••`. Each target also carries its most recent delivery outcome in `last_delivery` (or null if it has never fired).' operationId: listWebhooks responses: '200': description: All configured webhook targets content: application/json: schema: type: object additionalProperties: true example: targets: - id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e name: Eng on-call (Slack) type: slack enabled: true url_preview: https://hooks.slack.com/…BXqZ has_secret: false headers: null config: null rule_ids: - rule_inactivity_30m created_at: '2026-06-25T14:03:11.482Z' updated_at: '2026-06-25T14:03:11.482Z' last_delivery: status: success status_code: 200 attempts: 1 error: null created_at: '2026-06-25T18:41:55.117Z' - id: 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d name: Internal alert sink type: generic enabled: true url_preview: https://alerts.internal.example.com/…hook has_secret: true headers: X-Api-Key: •••• Authorization: •••• config: null rule_ids: null created_at: '2026-06-20T09:15:00.000Z' updated_at: '2026-06-24T22:48:30.901Z' last_delivery: status: failed status_code: 503 attempts: 3 error: Service Unavailable created_at: '2026-06-24T22:48:30.901Z' post: tags: - Webhooks summary: Create a webhook target description: Creates a webhook target that fires when alerts match. `name` and `type` are required. `url` is required for most providers but is derived or defaulted for a few (Telegram and Opsgenie derive it from config; PagerDuty defaults it) — consult GET /api/webhooks/providers for which fields each provider needs. `config` carries provider-specific params (e.g. `{ chat_id }` for Telegram, `{ routing_key, severity }` for PagerDuty, `{ api_key, region }` for Opsgenie). `secret` (HMAC-SHA256 signing) and custom `headers` apply only to the generic family and are silently ignored for other providers. `rule_ids` optionally scopes the target to specific alert rules; omit it to fire for all rules. The response is the created target, REDACTED the same way as the list endpoint (URL masked, secrets shown only as `has_secret`/`••••`). operationId: createWebhook requestBody: required: true content: application/json: schema: type: object required: - name - type properties: name: type: string type: type: string enum: - slack - discord - teams - google_chat - mattermost - rocketchat - telegram - pagerduty - opsgenie - splunk_oncall - zapier - make - n8n - pipedream - generic url: type: string format: uri description: Required for most providers; omit for those that derive their URL (Telegram, Opsgenie) or default it (PagerDuty). See GET /api/webhooks/providers. enabled: type: boolean default: true config: type: object additionalProperties: true description: Provider-specific params, e.g. { chat_id } (Telegram), { routing_key, severity } (PagerDuty), { api_key, region } (Opsgenie). secret: type: string description: 'Generic family only: HMAC-SHA256 signing secret' headers: type: object additionalProperties: type: string description: 'Generic family only: extra request headers' rule_ids: type: array items: type: string description: 'Optional: scope to specific alert rules (omit for all)' examples: slack: summary: Slack incoming webhook scoped to one rule value: name: Eng on-call (Slack) type: slack url: https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXXXXXXXXXXXXBXqZ enabled: true rule_ids: - rule_inactivity_30m pagerduty: summary: PagerDuty (URL defaulted, routing key in config) value: name: Sev1 pager type: pagerduty config: routing_key: R0ABCDEF0123456789ABCDEF01 severity: critical generic: summary: Generic webhook with HMAC secret and custom headers value: name: Internal alert sink type: generic url: https://alerts.internal.example.com/cc-monitor/hook secret: whsec_4f8c2a9e1b6d7f0a3c5e8b1d2f4a6c8e headers: X-Api-Key: k_live_abc123 Authorization: Bearer t0ken responses: '201': description: Created target (redacted) content: application/json: schema: type: object additionalProperties: true example: target: id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e name: Eng on-call (Slack) type: slack enabled: true url_preview: https://hooks.slack.com/…BXqZ has_secret: false headers: null config: null rule_ids: - rule_inactivity_30m created_at: '2026-06-25T14:03:11.482Z' updated_at: '2026-06-25T14:03:11.482Z' last_delivery: status: success status_code: 200 attempts: 1 error: null created_at: '2026-06-25T18:41:55.117Z' '400': description: Validation error /api/webhooks/{id}: patch: tags: - Webhooks summary: Update a webhook target (partial; type is immutable) description: Partially updates a webhook target. Only the keys present in the body are changed; omitted keys are left as-is. The provider `type` is immutable. `config` is MERGED over the existing config and re-validated, so a single field (e.g. `severity`, `region`) can change without re-sending the secret fields. For the generic family, `secret` may be omitted (keep current) or sent as `null` (clear it); `headers` and `rule_ids` replace their stored value when present. The response is the updated target, REDACTED (URL masked, secrets shown only as `has_secret`/`••••`). operationId: updateWebhook parameters: - name: id in: path required: true schema: type: string example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e requestBody: required: true content: application/json: schema: type: object properties: name: type: string url: type: string format: uri description: Omit to keep current enabled: type: boolean config: type: object additionalProperties: true description: Provider params; merged over existing (secrets kept if omitted) secret: type: - string - 'null' description: 'Generic family only: omit to keep, null to clear' headers: type: object additionalProperties: type: string rule_ids: type: array items: type: string examples: disable: summary: Disable a target without touching anything else value: enabled: false renameAndRescope: summary: Rename and re-scope to different rules value: name: Eng on-call (Slack) — muted weekends rule_ids: - rule_token_threshold_5m mergeConfig: summary: Change one PagerDuty config field (routing key kept) value: config: severity: warning clearSecret: summary: 'Generic family: clear the signing secret' value: secret: null responses: '200': description: Updated target (redacted) content: application/json: schema: type: object additionalProperties: true example: target: id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e name: Eng on-call (Slack) — muted weekends type: slack enabled: true url_preview: https://hooks.slack.com/…BXqZ has_secret: false headers: null config: null rule_ids: - rule_inactivity_30m created_at: '2026-06-25T14:03:11.482Z' updated_at: '2026-06-25T14:03:11.482Z' last_delivery: status: success status_code: 200 attempts: 1 error: null created_at: '2026-06-25T18:41:55.117Z' '400': description: Validation error '404': description: Target not found delete: tags: - Webhooks summary: Delete a webhook target and its delivery log description: 'Permanently deletes a webhook target. Its delivery-log history cascades away with it. Returns `{ ok: true }` on success. This only removes the delivery channel — alert *rules* and the fired-alert feed are untouched.' operationId: deleteWebhook parameters: - name: id in: path required: true schema: type: string example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e responses: '200': description: Deleted '404': description: Target not found /api/webhooks/{id}/test: post: tags: - Webhooks summary: Send a synthetic test alert to a target description: Sends a synthetic test alert to the target and reports the delivery outcome synchronously. The HTTP status is always 200 when the target exists — the *request itself* succeeded — and the `ok` flag carries the downstream delivery result. `status` is the HTTP status code returned by the provider (or null if the request never completed), `attempts` is how many tries were made (the delivery layer retries transient failures), and `error` is a human-readable failure reason or null on success. operationId: testWebhook parameters: - name: id in: path required: true schema: type: string example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e responses: '200': description: Delivery result (ok flag carries the downstream outcome) content: application/json: schema: type: object additionalProperties: true examples: success: summary: Provider accepted the test payload value: ok: true status: 200 attempts: 1 error: null failure: summary: Provider rejected or was unreachable after retries value: ok: false status: 503 attempts: 3 error: Service Unavailable '404': description: Target not found /api/webhooks/{id}/deliveries: get: tags: - Webhooks summary: Recent delivery log for a target description: Returns the recent delivery log for a target, newest first. Each row records the alert that fired, the resulting HTTP status, the number of attempts, any error text, and a timestamp. `limit` is clamped to 1–200 (default 20) and `offset` to ≥0 (default 0). This is an audit trail of past sends — it does not trigger a new delivery (use POST /{id}/test for that). operationId: listWebhookDeliveries parameters: - name: id in: path required: true schema: type: string example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e - name: limit in: query schema: type: integer default: 20 description: Max rows to return (clamped to 1–200). example: 20 - name: offset in: query schema: type: integer default: 0 description: Pagination offset (clamped to ≥0). example: 0 responses: '200': description: Delivery rows, newest first content: application/json: schema: type: object additionalProperties: true example: deliveries: - id: 5012 target_id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e alert_event_id: 8841 status: success status_code: 200 attempts: 1 error: null created_at: '2026-06-25T18:41:55.117Z' - id: 5008 target_id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e alert_event_id: 8839 status: failed status_code: 429 attempts: 3 error: Too Many Requests created_at: '2026-06-25T18:30:02.004Z' limit: 20 offset: 0 '404': description: Target not found /api/openapi.json: get: tags: - Documentation summary: Get OpenAPI specification JSON description: 'Returns this OpenAPI 3.0 document as JSON. Both API explorers consume it: Swagger UI (`/api/docs`) for interactive try-it-out requests, and ReDoc (`/api/redoc`) for a read-optimized reference.' operationId: getOpenApiJson responses: '200': description: OpenAPI document content: application/json: schema: type: object additionalProperties: true /api/docs: get: tags: - Documentation summary: Swagger UI explorer description: Interactive Swagger UI rendering of this specification, with try-it-out request execution against the live local server. operationId: getSwaggerUi responses: '200': description: Swagger UI HTML page content: text/html: schema: type: string /api/redoc: get: tags: - Documentation summary: ReDoc API reference description: Read-optimized, three-panel ReDoc rendering of this specification. The ReDoc bundle is served locally from `/api/redoc/redoc.standalone.js` (bundled with the server, never fetched from a CDN), so the reference works fully offline. operationId: getRedoc responses: '200': description: ReDoc HTML page content: text/html: schema: type: string /api/cc-config/overview: get: tags: - CcConfig summary: Config explorer overview description: Read-only. Returns the four resolved filesystem roots plus aggregate counts across every surface (skills, agents, commands, output styles, plugins, marketplaces, keybindings, MCP servers, hooks, memory, settings files). All sub-reads degrade to empty on any I/O error, so this endpoint never errors. operationId: ccConfigGetOverview parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Overview of roots and counts. content: application/json: schema: $ref: '#/components/schemas/CcConfigOverviewResponse' example: roots: claudeHome: /Users/son/.claude projectClaudeDir: /repo/.claude projectRoot: /repo claudeJson: /Users/son/.claude.json counts: skills: user: 12 project: 3 agents: user: 5 project: 2 commands: user: 8 project: 0 outputStyles: user: 1 project: 0 plugins: 8 pluginsEnabled: 6 pluginsDisabled: 1 marketplaces: 2 keybindings: 24 mcpServers: user: 3 project: 1 hooks: user: 2 project: 0 project-local: 1 memory: 5 settingsFiles: 2 /api/cc-config/skills: get: tags: - CcConfig summary: List discovered skills description: Read-only. Lists skill directories (each containing a SKILL.md) under the user and/or project roots, with parsed frontmatter and a 320-char body preview. Files over 256 KiB are read truncated. Degrades to an empty list on I/O errors. operationId: ccConfigGetSkills parameters: - name: scope in: query required: false schema: type: string enum: - user - project - all default: all description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.' example: all - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Skills list. content: application/json: schema: $ref: '#/components/schemas/CcConfigSkillsResponse' example: items: - scope: user name: code-reviewer path: /Users/son/.claude/skills/code-reviewer file: /Users/son/.claude/skills/code-reviewer/SKILL.md size: 2048 mtime: 1718900000000 truncated: false frontmatter: name: code-reviewer description: Reviews diffs preview: Use this skill to review pull requests... /api/cc-config/agents: get: tags: - CcConfig summary: List subagents description: Read-only. Lists single-file subagent definitions (/agents/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors. operationId: ccConfigGetAgents parameters: - name: scope in: query required: false schema: type: string enum: - user - project - all default: all description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.' example: all - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Agents list. content: application/json: schema: $ref: '#/components/schemas/CcConfigAgentsResponse' example: items: - scope: project name: backend-reviewer file: /repo/.claude/agents/backend-reviewer.md size: 1536 mtime: 1718900000000 truncated: false frontmatter: name: backend-reviewer model: sonnet preview: Review backend route and hook logic for regressions... /api/cc-config/commands: get: tags: - CcConfig summary: List slash commands description: Read-only. Lists slash-command definitions (/commands/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors. operationId: ccConfigGetCommands parameters: - name: scope in: query required: false schema: type: string enum: - user - project - all default: all description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.' example: all - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Commands list. content: application/json: schema: $ref: '#/components/schemas/CcConfigCommandsResponse' example: items: - scope: user name: commit file: /Users/son/.claude/commands/commit.md size: 800 mtime: 1718900000000 truncated: false frontmatter: description: Create a commit preview: Commit the staged changes with a descriptive message... /api/cc-config/output-styles: get: tags: - CcConfig summary: List output styles description: Read-only. Lists output-style definitions (/output-styles/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors. operationId: ccConfigGetOutputStyles parameters: - name: scope in: query required: false schema: type: string enum: - user - project - all default: all description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.' example: all - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Output styles list. content: application/json: schema: $ref: '#/components/schemas/CcConfigOutputStylesResponse' example: items: - scope: user name: concise file: /Users/son/.claude/output-styles/concise.md size: 420 mtime: 1718900000000 truncated: false frontmatter: name: concise preview: Respond tersely, no preamble... /api/cc-config/plugins: get: tags: - CcConfig summary: List installed plugins description: 'Read-only. Reads the user-scope plugins manifest (~/.claude/plugins/installed_plugins.json), resolves each instance''s on-disk contributions, and reports its tri-state enabled flag from settings.json. Read-only by design: plugins are written concurrently by the running Claude Code CLI. No scope/cwd parameters. Degrades to an empty plugin list when the manifest is missing.' operationId: ccConfigGetPlugins responses: '200': description: Plugins manifest summary and list. content: application/json: schema: $ref: '#/components/schemas/CcConfigPluginsResponse' example: manifestPath: /Users/son/.claude/plugins/installed_plugins.json manifestExists: true plugins: - key: superpowers@obra name: superpowers marketplace: obra scope: user version: 1.2.0 installPath: /Users/son/.claude/plugins/superpowers installedAt: '2026-05-01T12:00:00.000Z' lastUpdated: '2026-06-01T09:30:00.000Z' gitCommitSha: a1b2c3d4e5f6 installPathExists: true enabled: true contributes: skills: 3 agents: 2 commands: 5 outputStyles: 0 hooks: 1 pluginJson: name: superpowers version: 1.2.0 /api/cc-config/mcp: get: tags: - CcConfig summary: List MCP servers description: Read-only. Discovers MCP servers from ~/.claude.json (top-level and projects[]) and ~/.claude/settings.json, split into `user` and `projectScoped`. Header and env VALUES are never returned — only their key NAMES. Read-only by design (the CLI writes these files concurrently). Degrades to empty arrays on errors. operationId: ccConfigGetMcp parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: MCP servers grouped by scope. content: application/json: schema: $ref: '#/components/schemas/CcConfigMcpResponse' example: user: - name: github source: ~/.claude.json (top-level) kind: stdio command: npx args: - '-y' - '@modelcontextprotocol/server-github' envNames: - GITHUB_TOKEN projectScoped: - name: internal source: ~/.claude.json (projects[/repo]) kind: http url: https://mcp.example.com/sse headers: - Authorization /api/cc-config/hooks: get: tags: - CcConfig summary: List configured hooks description: Read-only. Reads hooks from the user, project, and project-local settings files. Known event types (SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStop, Notification, PreCompact) are flattened into matcher/type/command/timeout entries; unknown events are passed through verbatim. Hooks in settings are read-only here. Degrades to per-file `exists:false` on errors. operationId: ccConfigGetHooks parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Hook sources by scope. content: application/json: schema: $ref: '#/components/schemas/CcConfigHooksResponse' example: items: - scope: user file: /Users/son/.claude/settings.json exists: true hooks: PostToolUse: - matcher: '*' type: command command: node ~/.claude/hooks/post-tool-use.js timeout: null - scope: project file: /repo/.claude/settings.json exists: false hooks: {} /api/cc-config/settings: get: tags: - CcConfig summary: List settings files description: Read-only. Returns the user, project, and project-local settings.json contents with secret-like keys redacted to the literal `` (keys matching /token|secret|password|api[_-]?key|auth/i with string values). Missing files report `exists:false`. Settings files are read-only here (concurrent CLI writes). operationId: ccConfigGetSettings parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Settings sources by scope (secrets redacted). content: application/json: schema: $ref: '#/components/schemas/CcConfigSettingsResponse' example: items: - scope: user file: /Users/son/.claude/settings.json exists: true data: model: sonnet apiKey: raw_size: 412 - scope: project file: /repo/.claude/settings.json exists: false - scope: project-local file: /repo/.claude/settings.local.json exists: false /api/cc-config/memory: get: tags: - CcConfig summary: List memory files description: Read-only. Returns the two primary CLAUDE.md files (scopes `user`/`project`) plus every per-project file-based auto-memory markdown file under ~/.claude/projects//memory/ (scope `auto-memory`, sorted index files first). Previews are 480 chars; files over 256 KiB are truncated. Degrades to fewer files on errors. operationId: ccConfigGetMemory parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Memory items. content: application/json: schema: $ref: '#/components/schemas/CcConfigMemoryResponse' example: items: - scope: user file: /Users/son/.claude/CLAUDE.md size: 1200 mtime: 1718900000000 truncated: false preview: '# Workspace & Productivity System...' - scope: auto-memory project: '-Users-son-repo' name: MEMORY.md isIndex: true file: /Users/son/.claude/projects/-Users-son-repo/memory/MEMORY.md size: 980 mtime: 1718900000000 truncated: false frontmatter: {} preview: '## Persistent facts\n- Never run destructive ops without confirmation...' /api/cc-config/marketplaces: get: tags: - CcConfig summary: List plugin marketplaces description: Read-only. Reads ~/.claude/plugins/known_marketplaces.json and, best-effort, each marketplace's .claude-plugin/marketplace.json for plugin count and metadata. No scope/cwd parameters. Degrades to an empty list when the file is missing. operationId: ccConfigGetMarketplaces responses: '200': description: Known marketplaces summary and list. content: application/json: schema: $ref: '#/components/schemas/CcConfigMarketplacesResponse' example: knownPath: /Users/son/.claude/plugins/known_marketplaces.json knownExists: true items: - name: obra source: source: github repo: obra/superpowers-marketplace installLocation: /Users/son/.claude/plugins/marketplaces/obra lastUpdated: '2026-06-10T00:00:00.000Z' pluginCount: 12 marketplaceName: Superpowers marketplaceDescription: Curated agent skills marketplaceOwner: name: obra /api/cc-config/keybindings: get: tags: - CcConfig summary: Get keybindings description: Read-only. Parses ~/.claude/keybindings.json into context-scoped groups of key → action bindings. When the file is absent, returns only `{ file, exists:false }`. No scope/cwd parameters. operationId: ccConfigGetKeybindings responses: '200': description: Parsed keybindings (or exists:false). content: application/json: schema: $ref: '#/components/schemas/CcConfigKeybindingsResponse' example: file: /Users/son/.claude/keybindings.json exists: true schema: null docs: null groups: - context: editor bindings: - key: ctrl+s action: submit put: tags: - CcConfig summary: Overwrite keybindings description: 'Mutating. Overwrites ~/.claude/keybindings.json from a structured list of context groups (`{ context, bindings: [{ key, action }] }`); on disk the bindings become an object keyed by `key`. The existing file is backed up first (under cc-config-backups/keybindings/) and any top-level metadata (`$schema`, `$docs`, unknown keys) is preserved — only the `bindings` array is replaced. Rejects duplicate contexts and duplicate keys within a context. Writes are atomic. Unlike settings.json, keybindings.json is not rewritten mid-session by the live CLI, so this edit is safe. Emits a `cc_config_changed` websocket event on success.' operationId: ccConfigPutKeybindings requestBody: required: true content: application/json: schema: type: object required: - groups properties: groups: type: array description: Full set of context-scoped keybinding groups to persist. items: type: object required: - context - bindings properties: context: type: string example: Global bindings: type: array items: type: object required: - key - action properties: key: type: string example: ctrl+t action: type: string example: toggleTodos example: groups: - context: Global bindings: - key: ctrl+t action: toggleTodos - context: Chat bindings: - key: escape action: cancel responses: '200': description: keybindings.json written (backup taken when it already existed). content: application/json: schema: $ref: '#/components/schemas/CcConfigFileWriteResponse' example: ok: true file: /Users/son/.claude/keybindings.json target: /Users/son/.claude/keybindings.json backupPath: /Users/son/.claude/cc-config-backups/keybindings/keybindings.json.2026-06-25T12-00-00.000Z.bak created: false '400': description: Bad request. `EBADREQ` — `groups` is missing or not an array. `EBADCONTENT` — a group/binding is malformed, a context is empty, or there is a duplicate context or duplicate key within a context. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADCONTENT message: duplicate key "ctrl+t" in context Global '413': description: '`ETOOLARGE` — the serialized keybindings exceed the 256 KiB limit.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ETOOLARGE message: content exceeds 262144 bytes /api/cc-config/statusline: get: tags: - CcConfig summary: Get statusline config and scripts description: Read-only. Returns the `statusLine` block from ~/.claude/settings.json plus any statusline scripts (statusline.py, statusline-command.sh) with a 4000-char preview. Scripts over 256 KiB are truncated. No scope/cwd parameters. operationId: ccConfigGetStatusline responses: '200': description: Statusline config and scripts. content: application/json: schema: $ref: '#/components/schemas/CcConfigStatuslineResponse' example: config: type: command command: python3 ~/.claude/statusline.py scripts: - file: /Users/son/.claude/statusline.py size: 1280 mtime: 1718900000000 truncated: false preview: '#!/usr/bin/env python3\nimport json, sys\n...' /api/cc-config/hook-scripts: get: tags: - CcConfig summary: List hook scripts description: Read-only. Lists files in the ~/.claude/hooks/ directory (name, path, size, mtime), sorted by name. Returns metadata only — no file contents. Use GET /file to read an individual script. No scope/cwd parameters. operationId: ccConfigGetHookScripts responses: '200': description: Hook scripts directory listing. content: application/json: schema: $ref: '#/components/schemas/CcConfigHookScriptsResponse' example: dir: /Users/son/.claude/hooks items: - name: post-tool-use.js file: /Users/son/.claude/hooks/post-tool-use.js size: 640 mtime: 1718900000000 /api/cc-config/file: get: tags: - CcConfig summary: Read a single file body description: 'Read-only. Returns the body of one file. Strict path containment: the resolved absolute path MUST live under the Claude home, the project .claude dir, or (only for a file literally named CLAUDE.md) the project root. Bodies over 256 KiB are truncated. NOTE: there is no 404 path — a missing, unreadable, or out-of-root file all return 400.' operationId: ccConfigGetFile parameters: - name: path in: query required: true schema: type: string description: Absolute path to read. Must resolve under an allowed root; under the project root only CLAUDE.md is permitted. example: /Users/son/.claude/skills/code-reviewer/SKILL.md - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: File body with metadata. content: application/json: schema: $ref: '#/components/schemas/CcConfigFileReadResponse' example: ok: true file: /Users/son/.claude/skills/code-reviewer/SKILL.md truncated: false size: 2048 text: '---\nname: code-reviewer\n---\n# Code Reviewer\n...' mtime: 1718900000000 '400': description: Bad or denied path. `BAD_PATH` — `path` query is missing or empty. `READ_DENIED` — path is outside the allowed roots, only CLAUDE.md is readable from the project root, or the file is missing/unreadable (there is no 404). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: READ_DENIED message: path is outside allowed roots put: tags: - CcConfig summary: Create or overwrite a text artifact description: Mutating. Creates or overwrites a low-risk text artifact (skills, agents, commands, output-styles, memory, auto-memory). Before overwriting an existing artifact a timestamped backup is created under a cc-config-backups directory (well outside the dirs Claude Code scans); fresh creates have no backup (backupPath:null). Writes are atomic (temp file + rename). Names are validated against a strict allowlist and the resolved path is re-checked to be inside its containment root. Content must be a string ≤ 256 KiB (UTF-8). Plugins, MCP servers, hooks-in-settings, and settings.json are intentionally NOT writable. Emits a `cc_config_changed` websocket event on success. operationId: ccConfigPutFile requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CcConfigFileWriteRequest' example: scope: user type: agents name: my-reviewer content: '---\nname: my-reviewer\n---\n# My Reviewer\nReview the diff...' parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Artifact created or overwritten. content: application/json: schema: $ref: '#/components/schemas/CcConfigFileWriteResponse' example: ok: true file: /Users/son/.claude/agents/my-reviewer.md target: /Users/son/.claude/agents/my-reviewer.md backupPath: null created: true '400': description: Bad request. `EBADREQ` — scope/type missing or not strings. `EBADTYPE` — unknown type. `EBADSCOPE` — scope not user/project. `EBADNAME` — name fails the allowlist (or auto-memory name is not a flat *.md). `EBADPROJECT` — invalid auto-memory project slug. `EBADCONTENT` — content is not a string. `EOUTOFROOT` — resolved path escapes its containment root. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADNAME message: name must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/ '413': description: '`ETOOLARGE` — content exceeds the 256 KiB (262144-byte) limit.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ETOOLARGE message: content exceeds 262144 bytes '500': description: '`EINTERNAL` (or any unmapped error code) — unexpected filesystem error.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EINTERNAL message: 'EACCES: permission denied' delete: tags: - CcConfig summary: Delete a text artifact description: Mutating. Deletes a low-risk text artifact (same type/scope/name/project semantics as PUT, no content). A mandatory backup is created BEFORE deletion — if the backup fails the original is left intact. Skill deletes remove the whole skill directory. Emits a `cc_config_changed` websocket event on success. Plugins, MCP servers, hooks-in-settings, and settings.json are NOT deletable. operationId: ccConfigDeleteFile requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CcConfigFileDeleteRequest' example: scope: user type: agents name: my-reviewer parameters: - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Artifact deleted (backup taken first). content: application/json: schema: $ref: '#/components/schemas/CcConfigFileDeleteResponse' example: ok: true file: /Users/son/.claude/agents/my-reviewer.md target: /Users/son/.claude/agents/my-reviewer.md backupPath: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak '400': description: Bad request. `EBADREQ` — scope/type missing or not strings. `EBADTYPE` — unknown type. `EBADSCOPE` — scope not user/project. `EBADNAME` — invalid name. `EBADPROJECT` — invalid auto-memory project slug. `EOUTOFROOT` — resolved path escapes its containment root. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADTYPE message: 'unknown type: plugins' '404': description: '`ENOTFOUND` — the target artifact does not exist.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ENOTFOUND message: agents/my-reviewer does not exist '500': description: '`EINTERNAL` (or any unmapped error code) — unexpected filesystem error.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EINTERNAL message: 'EACCES: permission denied' /api/cc-config/backups: get: tags: - CcConfig summary: List artifact backups description: 'Read-only. Lists backups taken by the mutation endpoints, across the user and project roots plus per-project auto-memory backup dirs. Optional `scope`/`type` narrow the search. Results are sorted newest first. Best-effort: unreadable backup dirs are skipped, never erroring.' operationId: ccConfigGetBackups parameters: - name: scope in: query required: false schema: type: string enum: - user - project description: Narrow to a single root scope. Only `user` or `project` are honored; any other value is ignored (both scopes scanned). example: user - name: type in: query required: false schema: type: string enum: - skills - agents - commands - output-styles - memory - auto-memory description: Narrow to a single artifact type. When omitted, all types are scanned. example: agents - name: cwd in: query required: false schema: type: string description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd. example: /repo responses: '200': description: Backups sorted newest first. content: application/json: schema: $ref: '#/components/schemas/CcConfigBackupsResponse' example: items: - scope: user type: agents name: my-reviewer.md.2026-06-25T12-00-00.000Z.bak backupPath: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak isDir: false mtime: 1718900000000 size: 2048 /api/run: get: tags: - Run summary: List live + recently-exited runs description: Returns every run handle currently held in memory (newest first) plus concurrency telemetry (`maxConcurrent`, `activeCount`). Handles are reaped 5 minutes after the process exits, so terminal runs disappear from here but remain in GET /api/run/history. Read-only — no process is spawned. The loopback same-origin guard applies. operationId: runList responses: '200': description: Tracked runs and concurrency telemetry content: application/json: schema: $ref: '#/components/schemas/RunListResponse' example: items: - id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: 48213 mode: conversation cwd: /Users/dev/projects/my-app model: claude-opus-4-8 permissionMode: acceptEdits effort: high prompt: Refactor the auth module and add tests. argv: - '--output-format' - stream-json - '--verbose' - '--include-partial-messages' - '--permission-mode' - acceptEdits - '--input-format' - stream-json resumeSessionId: null status: running startedAt: 1718000000000 endedAt: null exitCode: null signal: null error: null sessionId: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d envelopeCount: 12 stdoutTail: | {"type":"assistant",...} stderrTail: '' maxConcurrent: 10000 activeCount: 1 '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed post: tags: - Run summary: Spawn a new run description: Spawns a real `claude` Code subprocess in the sanitised `cwd`. In `headless` mode the prompt is passed via argv and stdin is closed (one turn, then exit); in `conversation` mode stdin stays open for follow-ups via POST /api/run/{id}/message, and an existing session may be resumed with `resumeSessionId`. The child is always run with `--output-format stream-json --verbose --include-partial-messages`; parsed envelopes are broadcast as `run_stream` WebSocket messages and lifecycle transitions as `run_status`. A row is persisted to `dashboard_runs`, and the in-memory handle is reaped 5 minutes after exit. Concurrency is capped (ECONCURRENCY -> 429). The loopback same-origin guard applies. Returns the freshly-created RunHandle. operationId: runSpawn requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RunSpawnRequest' example: prompt: Refactor the auth module and add tests. mode: conversation cwd: /Users/dev/projects/my-app model: claude-opus-4-8 effort: high permissionMode: acceptEdits responses: '201': description: Run spawned; returns the new run handle (status `spawning`). content: application/json: schema: $ref: '#/components/schemas/RunHandle' example: id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: 48213 mode: conversation cwd: /Users/dev/projects/my-app model: claude-opus-4-8 permissionMode: acceptEdits effort: high prompt: Refactor the auth module and add tests. argv: - '--output-format' - stream-json - '--verbose' - '--include-partial-messages' - '--permission-mode' - acceptEdits - '--input-format' - stream-json - '--model' - claude-opus-4-8 - '--effort' - high resumeSessionId: null status: spawning startedAt: 1718000000000 endedAt: null exitCode: null signal: null error: null sessionId: null envelopeCount: 0 stdoutTail: '' stderrTail: '' '400': description: Invalid spawn request — EBADPROMPT (prompt required), EBADCWD (cwd not an existing absolute directory), EBADMODE (bad mode, or resumeSessionId outside conversation mode), EBADEFFORT (effort not low/medium/high/xhigh/max), or EBADSESSION (resumeSessionId not a valid session id). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: badPrompt: value: error: code: EBADPROMPT message: prompt is required badCwd: value: error: code: EBADCWD message: cwd must be an absolute path badMode: value: error: code: EBADMODE message: mode must be "headless" or "conversation" badEffort: value: error: code: EBADEFFORT message: 'effort must be one of: low, medium, high, xhigh, max' badSession: value: error: code: EBADSESSION message: resumeSessionId is not a valid session id '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed '429': description: Concurrency cap reached (ECONCURRENCY). The body additionally carries a top-level `running` array listing the runs currently holding the cap. content: application/json: schema: $ref: '#/components/schemas/RunSpawnConcurrencyResponse' example: error: code: ECONCURRENCY message: concurrency limit 10000 reached running: - id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: 48213 startedAt: 1718000000000 mode: conversation '500': description: Unexpected spawn failure (EINTERNAL). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EINTERNAL message: spawn claude ENOENT /api/run/history: get: tags: - Run summary: List persisted run history description: 'Returns the persistent history of runs spawned via the dashboard, sourced from the `dashboard_runs` sqlite table (newest first). Unlike GET /api/run, this survives the 5-minute in-memory reap so past runs stay visible and resumable for days. Each item is cross-referenced against live handles to set `isLive`. Returns `{ items: [] }` if the persistence DB is unavailable. Read-only — no process is spawned. The loopback same-origin guard applies.' operationId: runHistory parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 default: 50 description: Maximum number of history rows to return (default 50, clamped to 1–500). example: 50 - name: laneId in: query required: false schema: type: integer description: Return only the runs started through this lane (POST /api/lanes/{id}/start). Omit for every run; runs spawned from POST /api/run carry no lane and are excluded whenever this is set. example: 5 responses: '200': description: Run history, most recent first content: application/json: schema: $ref: '#/components/schemas/RunHistoryResponse' example: items: - id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c session_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d mode: conversation cwd: /Users/dev/projects/my-app model: claude-opus-4-8 permission_mode: acceptEdits effort: high resume_session_id: null prompt_preview: Refactor the auth module and add tests. status: completed exit_code: 0 started_at: '2026-06-25T17:00:00.000Z' ended_at: '2026-06-25T17:01:42.500Z' isLive: false lane_id: null '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed /api/run/cwds: get: tags: - Run summary: Suggest working directories description: 'Suggests plausible working directories for the Run launcher: the dashboard server''s cwd (always first), $HOME, and distinct recent cwds Claude Code has been used in (from the sessions table). Only directories that still exist on disk are returned; the DB lookup is best-effort. Read-only — no process is spawned. The loopback same-origin guard applies.' operationId: runCwds parameters: - name: q in: query required: false schema: type: string description: Optional case-insensitive substring filter (also applied client-side). example: my-app responses: '200': description: Working-directory suggestions content: application/json: schema: $ref: '#/components/schemas/RunCwdsResponse' example: items: - kind: dashboard path: /Users/dev/Claude-Code-Agent-Monitor label: Dashboard server - kind: home path: /Users/dev label: Home - kind: recent path: /Users/dev/projects/my-app label: my-app '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed /api/run/files: get: tags: - Run summary: Autocomplete files within a cwd description: Walks the given `cwd` and returns up to 40 file paths (relative to that cwd, shortest first) for the prompt editor's `@` references. The cwd is validated via the same sanitiser as spawning (must be an existing absolute directory). Dotfiles (except .env/.gitignore) and heavy build dirs (node_modules, .git, dist, build, out, .next, coverage, target, .venv, __pycache__, etc.) are skipped; the walk is bounded (≤5000 entries visited). Read-only — no process is spawned. The loopback same-origin guard applies. operationId: runFiles parameters: - name: cwd in: query required: false schema: type: string description: Absolute directory to walk. Must exist; defaults to the dashboard server's cwd when omitted. Invalid values yield 400 EBADCWD. example: /Users/dev/projects/my-app - name: q in: query required: false schema: type: string description: Optional case-insensitive substring filter applied to the relative path. example: route responses: '200': description: Matching file paths relative to cwd content: application/json: schema: $ref: '#/components/schemas/RunFilesResponse' example: items: - package.json - src/index.ts - server/routes/run.js '400': description: 'Invalid cwd (EBADCWD): not a string, not absolute, or does not exist.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADCWD message: 'cwd does not exist: /no/such/dir' '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed /api/run/binary: get: tags: - Run summary: Check whether the `claude` binary is on PATH description: Probes PATH (via which/where) for the `claude` binary so the UI can warn before the user clicks Run. The binary is NOT invoked — only resolved. Read-only — no process is spawned beyond the lookup. The loopback same-origin guard applies. operationId: runBinary responses: '200': description: Binary resolution result content: application/json: schema: $ref: '#/components/schemas/RunBinaryResponse' example: found: true path: /usr/local/bin/claude '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed /api/run/{id}/message: post: tags: - Run summary: Send a follow-up turn to a conversation run description: 'Writes a stream-json user envelope to a running conversation''s stdin and broadcasts a `run_input_ack` WebSocket message. Only valid for `conversation`-mode runs that are still `running`/`spawning` with writable stdin. SIDE EFFECT: the child resumes processing the new turn and emits further `run_stream`/`run_status` messages. The loopback same-origin guard applies.' operationId: runSendMessage parameters: - name: id in: path required: true schema: type: string format: uuid description: Run id (RunHandle.id). example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RunMessageRequest' example: text: Also update the README to document the new flags. responses: '200': description: Turn written to stdin content: application/json: schema: $ref: '#/components/schemas/RunMessageResponse' example: messageId: 9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f '400': description: Cannot deliver input — EBADINPUT (text required), EWRONGMODE (run is not conversation mode), ENOTRUNNING (run is not running/spawning), or ESTDINCLOSED (child stdin not writable). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: badInput: value: error: code: EBADINPUT message: text is required wrongMode: value: error: code: EWRONGMODE message: only conversation mode accepts follow-up input notRunning: value: error: code: ENOTRUNNING message: run is completed stdinClosed: value: error: code: ESTDINCLOSED message: stdin is not writable '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed '404': description: No run with this id (ENOTFOUND) — it never existed or was reaped. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ENOTFOUND message: run not found /api/run/{id}: get: tags: - Run summary: Get a single run description: Returns the in-memory handle for one run. Pass `?envelopes=1` to additionally include the bounded (≤500) stream-json envelope replay buffer so a client can re-attach to an in-flight run and see what it missed. Available only while the handle is live or within the 5-minute post-exit reap window — afterwards use GET /api/run/history. Read-only — no process is spawned. The loopback same-origin guard applies. operationId: runGet parameters: - name: id in: path required: true schema: type: string format: uuid description: Run id (RunHandle.id). example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c - name: envelopes in: query required: false schema: type: string enum: - '1' description: Set to `1` to include the `envelopes` replay buffer in the response. example: '1' responses: '200': description: The run handle (with `envelopes` appended when `?envelopes=1`). content: application/json: schema: oneOf: - $ref: '#/components/schemas/RunHandle' - $ref: '#/components/schemas/RunHandleWithEnvelopes' example: id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c pid: 48213 mode: conversation cwd: /Users/dev/projects/my-app model: claude-opus-4-8 permissionMode: acceptEdits effort: high prompt: Refactor the auth module and add tests. argv: - '--output-format' - stream-json - '--verbose' - '--include-partial-messages' resumeSessionId: null status: running startedAt: 1718000000000 endedAt: null exitCode: null signal: null error: null sessionId: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d envelopeCount: 12 stdoutTail: | {"type":"assistant",...} stderrTail: '' envelopes: - type: system subtype: init session_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d - type: assistant message: role: assistant content: - type: text text: Working on it... '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed '404': description: No run with this id (ENOTFOUND) — it never existed or was reaped. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ENOTFOUND message: run not found delete: tags: - Run summary: Kill a run description: 'Terminates a live run: sends SIGTERM (escalating to SIGKILL after ~5s if needed), sets the handle status to `killed`, broadcasts a `run_status` WebSocket message, persists the status to `dashboard_runs`, and schedules the handle for the 5-minute reap. Idempotent — returns `{ ok: true }` even if the run had already terminated. The loopback same-origin guard applies.' operationId: runKill parameters: - name: id in: path required: true schema: type: string format: uuid description: Run id (RunHandle.id). example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c responses: '200': description: Kill issued (or run already terminated). content: application/json: schema: $ref: '#/components/schemas/RunKillResponse' example: ok: true '403': description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: EBADORIGIN message: cross-origin requests are not allowed '404': description: No run with this id (ENOTFOUND) — it never existed or was reaped. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: ENOTFOUND message: run not found /api/lanes/ensure: post: tags: - Lanes summary: Find or adopt the lane owning a working directory description: 'Idempotent: returns the lane whose cwd is an exact match or the longest path-boundary parent of `cwd` with `created: false` (200), otherwise creates an `adopted` lane and returns it with `created: true` (201). The Workspace page opens on a directory rather than a lane id, so this is how it gets exactly one lane for that directory. Concurrent calls for the same path resolve to ONE lane: the `lanes.cwd` UNIQUE constraint decides, and the loser re-reads and returns the winner''s lane.' operationId: ensureLane requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LaneEnsureRequest' responses: '200': description: An existing lane already owns that cwd. content: application/json: schema: type: object required: - lane - created properties: lane: type: object additionalProperties: true created: type: boolean enum: - false '201': description: No lane owned that cwd, so an adopted one was created. content: application/json: schema: type: object required: - lane - created properties: lane: type: object additionalProperties: true created: type: boolean enum: - true '400': description: cwd is missing or not an absolute path (EBADCWD). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: The browser request was not same-origin/loopback. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/lanes/worktree: post: tags: - Lanes summary: Provision a managed git worktree lane description: Validates the absolute source repository, creates a managed lane in `provisioning` state, and returns immediately. Worktree creation continues under the lane lock; the existing `lane_update` broadcast reports either `idle` or `failed` with git stderr in `notes`. operationId: createLaneWorktree requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LaneWorktreeCreateRequest' responses: '202': description: Managed lane accepted for background provisioning. content: application/json: schema: type: object required: - lane properties: lane: type: object additionalProperties: true '400': description: sourceRepo is relative, missing, or not a git repository. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: The browser request was not same-origin/loopback. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: The computed worktree directory already belongs to a lane, or no unique directory was available after 50 attempts. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/lanes/{id}: patch: tags: - Lanes summary: Partially update a lane description: Updates lane fields, including run_id; browser requests must pass the loopback same-origin guard. The provisioning-time facts kind, source_repo, slug and base_branch are NOT patchable — kind is check 1 of the destroy guard — and are silently ignored here. operationId: updateLane parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: type: object additionalProperties: true responses: '200': description: Updated lane. '400': description: kind was not one of adopted|managed (EBADKIND). '403': description: The browser request was not same-origin/loopback. '404': description: Lane not found. /api/lanes/{id}/preflight: get: tags: - Lanes summary: Count facts before a destructive lane action operationId: preflightLaneAction parameters: - name: id in: path required: true schema: type: integer - name: action in: query required: true schema: type: string enum: - reset - remove - purge responses: '200': description: 'Current counted facts for the selected action. reset/remove additionally return blocked[] (hard blockers: adopted, missing, unreadable, unpushed-commits — the only one force overrides) and warnings[] (informational only, e.g. no-remote).' '400': description: Unknown action. '404': description: Lane not found. /api/lanes/{id}/git: get: tags: - Lanes summary: A lane's working-copy facts description: Branch, short HEAD, that commit's subject, and the uncommitted counts for the lane's cwd. Read-only, so no same-origin guard. Kept out of GET /api/lanes because it shells out to git three times and that payload is polled and re-broadcast on every lane_update. A cwd that is missing, is not a git repository, or makes git fail returns available:false with HTTP 200 — a lane pointing at a plain directory is a normal state, not a fault. operationId: getLaneGitFacts parameters: - name: id in: path required: true schema: type: integer responses: '200': description: available:true with {branch, head, subject, dirty, untracked}, or available:false alone. '404': description: Lane not found. /api/lanes/branches: get: tags: - Lanes summary: A candidate source repo's local branches description: 'Feeds the Add-lane picker: given a repo path, returns its local branches (not origin/* refs — those aren''t checkout-able into a new worktree without a fetch first) plus which one is currently checked out. Same repo validation as POST /api/lanes/worktree; a repo neither endpoint can resolve can''t be provisioned from either. Read-only, no same-origin guard.' operationId: listLaneBranches parameters: - name: repo in: query required: true schema: type: string description: Absolute path to an existing git repository. responses: '200': description: '{branches: string[], current: string | null}.' '400': description: repo is missing, relative, does not exist, or is not a git repo. /api/lanes/{id}/{action}: post: tags: - Lanes summary: Confirm a reset, managed-worktree removal, or session purge description: Actions run under the lane lock after waiting for the lane child's actual exit. A failed spawn is already exited because no child started. Reset requires a live managed worktree; remove tears down a managed worktree, prunes git's stale record when the directory was deleted by hand, or only forgets an adopted-lane row without touching its directory. Reset/remove require force when managed work has unpushed commits. operationId: runLaneDestructiveAction parameters: - name: id in: path required: true schema: type: integer - name: action in: path required: true schema: type: string enum: - reset - remove - purge requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LaneDestructiveActionRequest' responses: '200': description: Action completed; purge includes its deleted-row counts. '400': description: Confirmation or complete expect facts missing, or the managed-worktree guard refused the target. '403': description: The browser request was not same-origin/loopback. '409': description: Preflight facts changed (ESTALE) or force is required (EUNPUSHED). '500': description: Git, run-exit timeout, or internal failure; git failures include error.stderr. /api/push/vapid-public-key: get: tags: - Push summary: Get the VAPID public key description: Returns the server's VAPID public application server key so a browser can register a Web Push subscription via `PushManager.subscribe({ applicationServerKey })`. The key pair is generated once and persisted in the shared data directory alongside the SQLite DB, so the web app and native apps reuse a single key pair across restarts. No authentication — this is a local-first dashboard. Safe to call repeatedly; always returns the same key. operationId: pushVapidPublicKey responses: '200': description: The VAPID public key content: application/json: schema: $ref: '#/components/schemas/PushVapidKeyResponse' example: publicKey: BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8 /api/push/subscribe: post: tags: - Push summary: Register a Web Push subscription description: 'Stores a browser PushSubscription so future `/api/push/send` broadcasts reach this endpoint. Persisted with `INSERT OR REPLACE INTO push_subscriptions (endpoint, p256dh, auth)`, keyed on `endpoint` — so the operation is idempotent: re-subscribing the same endpoint overwrites its keys instead of creating a duplicate. No authentication (local-first). Requires `endpoint`, `keys.p256dh`, and `keys.auth`; any missing field returns 400.' operationId: pushSubscribe requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PushSubscribeRequest' example: endpoint: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ keys: p256dh: BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM auth: tBHItJI5svbpez7KI4CCXg responses: '200': description: Subscription stored (created or overwritten) content: application/json: schema: $ref: '#/components/schemas/PushSubscribeResponse' example: ok: true '400': description: Missing required fields (one of `endpoint`, `keys.p256dh`, `keys.auth` was absent) content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: Missing required fields delete: tags: - Push summary: Remove a Web Push subscription description: 'Deletes a stored subscription so it stops receiving broadcasts. The endpoint identifier is supplied in the request BODY (a DELETE with a JSON body), NOT as a query parameter. Idempotent — deleting an endpoint that is not stored still returns `{ ok: true }`. No authentication (local-first). A missing `endpoint` returns 400.' operationId: pushUnsubscribe requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PushUnsubscribeRequest' example: endpoint: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ responses: '200': description: Subscription removed (or no-op if it was not stored) content: application/json: schema: $ref: '#/components/schemas/PushOkResponse' example: ok: true '400': description: Missing endpoint in the request body content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: Missing endpoint /api/push/send: post: tags: - Push summary: Broadcast a notification to all surfaces description: 'Dispatches a notification to every reachable surface at once: it fires a native OS notification via Electron''s main-process Notification API when the server is hosted inside the desktop app, AND sends an encrypted Web Push delivery to every stored subscription. Both legs run unconditionally so whichever surface the user is on receives the alert — under `npm start` the native leg is a no-op, and under the desktop app the Web Push leg is typically a no-op (Electron has no FCM credentials, so `push_subscriptions` is empty). Subscriptions rejected with HTTP 410 (Gone) are pruned from `push_subscriptions` during the request. The response reports `{ native, pushed, failed }` so the caller can tell a real delivery from a silent no-op. No authentication (local-first). A missing `title` or `body` returns 400; an unexpected dispatch error returns 500.' operationId: pushSend requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PushSendRequest' example: title: Session completed body: Your Claude Code session finished with 3 subagents. responses: '200': description: Dispatch ran; the body reports which surfaces fired and how many Web Push deliveries succeeded/failed content: application/json: schema: $ref: '#/components/schemas/PushSendResponse' example: ok: true native: false pushed: 2 failed: 1 '400': description: Missing title or body content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: Missing title or body '500': description: Dispatch error while broadcasting the notification content: application/json: schema: $ref: '#/components/schemas/MessageErrorResponse' example: error: message: Push service unavailable /api/sessions/facets: get: tags: - Sessions summary: List session facet values description: 'Returns the distinct facet values for the Sessions page filters: the non-empty working directories (the `cwd` column, sorted ascending) in `cwds`, and the distinct data-source ids (the `sessions.source` column) in `sources`. `sources` always includes the built-in `local` history plus any configured remote SSH machines. Always returns a 200 with (possibly empty) arrays.' operationId: listSessionFacets responses: '200': description: Distinct session working directories and data-source ids content: application/json: schema: $ref: '#/components/schemas/SessionsFacetsResponse' example: cwds: - /Users/son/WebstormProjects/Claude-Code-Agent-Monitor - /Users/son/code/another-project sources: - local - 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 /api/remote-sources: get: tags: - Remote Sources summary: List remote data sources description: Returns every configured remote SSH source the dashboard pulls Claude Code history from. NO secrets are ever returned — these records store none (SSH auth defers to the host SSH stack). Each entry carries its last sync `status`, `last_error`, `last_sync_at`, and `last_sync_counts`. Read-only; always 200. operationId: listRemoteSources responses: '200': description: All configured remote sources content: application/json: schema: $ref: '#/components/schemas/RemoteSourceListResponse' example: sources: - id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 label: Work laptop host: son@studio.local ssh_port: 22 identity_file: ~/.ssh/id_ed25519 remote_home: ~/.claude enabled: true status: ok last_error: null last_sync_at: '2026-07-22T18:41:55.117Z' last_sync_counts: imported: 9 skipped: 41 backfilled: 0 errors: 0 sessions_seen: 50 sessions_tagged: 50 created_at: '2026-07-20T09:15:00.000Z' updated_at: '2026-07-22T18:41:55.117Z' post: tags: - Remote Sources summary: Register a remote data source description: 'Registers a remote SSH source. `label` and `host` are required; `host` is an SSH destination (`user@host`) or a `~/.ssh/config` alias. Optional `ssh_port`, `identity_file`, `remote_home`, and `enabled` fine-tune the connection. No credentials are accepted or stored — auth defers to the host SSH stack. Returns the created source (201). Validation failures return 400 `{ error: { code, message } }` with one of the codes INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME.' operationId: createRemoteSource requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RemoteSourceCreateRequest' examples: minimal: summary: Minimal — label + SSH config alias value: label: Work laptop host: studio full: summary: Full — explicit port, key, and remote home value: label: Work laptop host: son@studio.local ssh_port: 22 identity_file: ~/.ssh/id_ed25519 remote_home: ~/.claude enabled: true responses: '201': description: Remote source created content: application/json: schema: $ref: '#/components/schemas/RemoteSourceResponse' example: source: id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 label: Work laptop host: son@studio.local ssh_port: 22 identity_file: ~/.ssh/id_ed25519 remote_home: ~/.claude enabled: true status: idle last_error: null last_sync_at: null last_sync_counts: null created_at: '2026-07-22T18:41:55.117Z' updated_at: '2026-07-22T18:41:55.117Z' '400': description: 'Validation error (codes: INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME).' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: label: summary: Missing/blank label value: error: code: INVALID_LABEL message: '`label` is required' host: summary: Missing/invalid host value: error: code: INVALID_HOST message: '`host` is required' port: summary: Port out of range value: error: code: INVALID_PORT message: '`ssh_port` must be 1–65535' /api/remote-sources/{id}: patch: tags: - Remote Sources summary: Update a remote data source (partial) description: Partially updates a remote source. Only the keys present in the body are changed; omitted keys are left as-is. The same validation as create applies to any field that is present. Returns the updated source, or 404 when the id is unknown. operationId: updateRemoteSource parameters: - name: id in: path required: true schema: type: string description: Remote source id. example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RemoteSourceUpdateRequest' examples: disable: summary: Disable a source without touching anything else value: enabled: false rename: summary: Rename and change port value: label: Studio Mac ssh_port: 2222 responses: '200': description: Updated remote source content: application/json: schema: $ref: '#/components/schemas/RemoteSourceResponse' example: source: id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 label: Studio Mac host: son@studio.local ssh_port: 2222 identity_file: ~/.ssh/id_ed25519 remote_home: ~/.claude enabled: false status: ok last_error: null last_sync_at: '2026-07-22T18:41:55.117Z' last_sync_counts: imported: 9 skipped: 41 backfilled: 0 errors: 0 sessions_seen: 50 sessions_tagged: 50 created_at: '2026-07-20T09:15:00.000Z' updated_at: '2026-07-22T19:02:10.400Z' '400': description: 'Validation error (codes: INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME).' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_PORT message: '`ssh_port` must be 1–65535' '404': description: Remote source not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Remote source not found delete: tags: - Remote Sources summary: Delete a remote data source description: Deletes a remote source. By default its imported sessions are DETACHED — reassigned to the built-in `local` source — so history is preserved. Pass `?purge=true` to instead permanently DELETE that source's imported sessions along with the source. The response reports whether a purge occurred. operationId: deleteRemoteSource parameters: - name: id in: path required: true schema: type: string description: Remote source id. example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 - name: purge in: query required: false schema: type: boolean default: false description: When true, also delete this source's imported sessions. When false/omitted, those sessions are reattached to `local`. example: true responses: '200': description: Deleted (with the purge outcome) content: application/json: schema: type: object required: - ok - purged properties: ok: type: boolean example: true purged: type: boolean description: True when the source's imported sessions were deleted (purge=true); false when they were detached to `local`. example: false examples: detached: summary: Default — sessions detached to local value: ok: true purged: false purged: summary: purge=true — sessions deleted value: ok: true purged: true '404': description: Remote source not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Remote source not found /api/remote-sources/{id}/test: post: tags: - Remote Sources summary: Probe SSH connectivity to a remote source description: Runs an SSH connectivity probe against the source and reports the outcome synchronously. The `ok` flag carries the probe result and `message` is a human-readable summary; on success `remoteProjects` may list the remote project directories discovered under the remote Claude home. This does not import anything — use POST /{id}/sync to pull. operationId: testRemoteSource parameters: - name: id in: path required: true schema: type: string description: Remote source id. example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 responses: '200': description: Probe result (ok flag carries the connectivity outcome) content: application/json: schema: $ref: '#/components/schemas/RemoteSourceTestResponse' examples: success: summary: Reachable value: ok: true message: Connected; found 24 project directories under ~/.claude/projects. remoteProjects: - '-Users-son-code-foo' - '-Users-son-code-bar' failure: summary: Unreachable / auth failed value: ok: false message: 'ssh: connect to host studio.local port 22: Connection refused' '404': description: Remote source not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Remote source not found /api/remote-sources/{id}/sync: post: tags: - Remote Sources summary: Pull Claude Code history from a remote source now description: Triggers an immediate pull of Claude Code history from the remote source over SSH, importing new transcripts through the same idempotent, baseline-preserving pipeline used for local imports and tagging imported sessions with this source's id. The response reports the per-run counters. Sync progress/completion is also broadcast over the WebSocket as `remote_source.status` frames. operationId: syncRemoteSource parameters: - name: id in: path required: true schema: type: string description: Remote source id. example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11 responses: '200': description: Sync result content: application/json: schema: $ref: '#/components/schemas/RemoteSourceSyncResponse' example: ok: true imported: 9 skipped: 41 backfilled: 0 errors: 0 sessions_seen: 50 sessions_tagged: 50 '404': description: Remote source not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: NOT_FOUND message: Remote source not found '500': description: Sync failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: SYNC_FAILED message: ssh exited with code 255 /api/remote-sources/sync-all: post: tags: - Remote Sources summary: Sync all enabled remote sources now description: Pulls Claude Code history from every enabled remote source over SSH, sequentially (one connection at a time). Per-source failures are isolated — one unreachable source never aborts the others — and each outcome is returned in `results`. Progress/completion is also broadcast over the WebSocket as `remote_source.status` frames. Always returns 200. operationId: syncAllRemoteSources responses: '200': description: Per-source sync outcomes content: application/json: schema: type: object properties: ok: type: boolean synced: type: integer description: Number of enabled sources that were attempted. results: type: array items: type: object properties: id: type: string ok: type: boolean error: type: string example: ok: true synced: 2 results: - id: src_a ok: true /api/settings/claude-home: get: tags: - Settings summary: Get the active Claude Code home directory description: Returns the Claude Code home directory the dashboard uses to locate transcripts and settings. Resolves to the CLAUDE_HOME environment variable when set, otherwise `/.claude`. Always returns 200. operationId: getClaudeHome responses: '200': description: Current Claude Code home directory content: application/json: schema: $ref: '#/components/schemas/SettingsClaudeHomeResponse' example: claude_home: /Users/son/.claude put: tags: - Settings summary: Update the Claude Code home directory description: Changes the Claude Code home directory used for transcript/settings discovery. A leading `~/` in `path` is expanded to the OS home directory; the resolved value must be an absolute path that exists and is a directory. On success the new value is applied to process.env immediately (so subsequent reads use it) and persisted to the project `.env` file. Returns 400 INVALID_PATH when `path` is missing/not a string, or when the resolved path is not absolute, does not exist, or is not a directory. operationId: updateClaudeHome requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SettingsClaudeHomeUpdateRequest' example: path: ~/.codefuse/engine/cc responses: '200': description: Claude Code home updated content: application/json: schema: $ref: '#/components/schemas/SettingsClaudeHomeUpdateResponse' example: ok: true claude_home: /Users/son/.codefuse/engine/cc '400': description: Invalid path — `path` missing or not a string, or the resolved path is not absolute / does not exist / is not a directory (code INVALID_PATH). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: INVALID_PATH message: 'Directory does not exist: /Users/son/.codefuse/engine/cc' /api/workflows/runs: get: tags: - Workflows summary: List Workflow-tool runs description: Returns a paginated list of Workflow-tool runs (fleets of sub-agents spawned by the Claude Code 'Workflow' tool / self-paced /loop), newest first. Filter by `status` (the literal `all` is treated as no filter) or by `session_id`; `session_id` takes precedence over `status` when both are supplied. `counts` is always the whole-table breakdown by status. JSON-blob columns (`phases`, `progress`) are parsed into arrays in each run. operationId: listWorkflowRuns parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 1000 default: 50 description: Page size, clamped to 1–1000 (default 50). - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 description: Pagination offset (clamped to >= 0). - name: status in: query required: false schema: type: string description: Filter by run status (open string, e.g. running | completed | error | failed). The literal value `all` is treated as no filter. - name: session_id in: query required: false schema: type: string description: Filter to runs launched by this session. Takes precedence over `status` when both are provided. responses: '200': description: Paginated list of workflow runs with status counts content: application/json: schema: $ref: '#/components/schemas/WorkflowRunsListResponse' example: runs: - run_id: wf_a1b2c3d4 session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 task_id: ISSUE-167 name: Refactor pricing engine status: completed default_model: claude-opus-4-8 started_at: '2026-06-25T18:04:11.122Z' ended_at: '2026-06-25T18:09:47.530Z' duration_ms: 336408 agent_count: 6 total_tokens: 1284750 total_tool_calls: 412 phases: - name: plan status: completed progress: - step: 1 label: scaffold done: true script_path: null journal_path: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json source: journal created_at: '2026-06-25T18:09:48.001Z' updated_at: '2026-06-25T18:09:48.001Z' total: 42 counts: completed: 30 error: 5 running: 7 limit: 50 offset: 0 '500': description: Failed to list workflow runs (code WORKFLOW_LIST_FAILED). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: WORKFLOW_LIST_FAILED message: 'no such table: workflows' /api/workflows/runs/{runId}: get: tags: - Workflows summary: Get a Workflow-tool run with its agents and events description: Returns one Workflow-tool run (by `run_id`) together with its linked inner agents and the events attributed to those agents (chronological, capped at 5000 rows). The run's JSON-blob columns (`phases`, `progress`) are parsed into arrays. Returns 404 WORKFLOW_NOT_FOUND when no run matches the id. operationId: getWorkflowRun parameters: - name: runId in: path required: true schema: type: string description: The workflow run id (workflows.run_id). responses: '200': description: Workflow run with inner agents and their events content: application/json: schema: $ref: '#/components/schemas/WorkflowRunDetailResponse' example: workflow: run_id: wf_a1b2c3d4 session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 task_id: ISSUE-167 name: Refactor pricing engine status: completed default_model: claude-opus-4-8 started_at: '2026-06-25T18:04:11.122Z' ended_at: '2026-06-25T18:09:47.530Z' duration_ms: 336408 agent_count: 6 total_tokens: 1284750 total_tool_calls: 412 phases: - name: plan status: completed progress: - step: 1 label: scaffold done: true script_path: null journal_path: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json source: journal created_at: '2026-06-25T18:09:48.001Z' updated_at: '2026-06-25T18:09:48.001Z' agents: - id: agent-7f1c session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 name: implementer type: subagent subagent_type: general-purpose status: completed task: Implement pricing changes current_tool: null started_at: '2026-06-25T18:04:30.000Z' ended_at: '2026-06-25T18:08:12.000Z' parent_agent_id: null metadata: null updated_at: '2026-06-25T18:08:12.000Z' awaiting_input_since: null awaiting_reason: null events: - id: 90211 session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11 agent_id: agent-7f1c event_type: PostToolUse tool_name: Edit summary: Edited server/routes/pricing.js data: null created_at: '2026-06-25T18:05:02.144Z' '404': description: No workflow run matches the id (code WORKFLOW_NOT_FOUND). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: WORKFLOW_NOT_FOUND message: Workflow run not found '500': description: Failed to load workflow run detail (code WORKFLOW_DETAIL_FAILED). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: WORKFLOW_DETAIL_FAILED message: database is locked /api/settings/import: post: tags: - Settings summary: Restore (import) a previously exported data bundle description: 'Restores a bundle produced by GET /api/settings/export. Supply it either as `multipart/form-data` with a single `file` field (browser upload) or as a JSON body `{ "path": "" }` (the server reads the file from disk — used by the `ccam import-data` CLI, and it also sidesteps the global 1 MB JSON body cap for large bundles). The restore is idempotent and NON-DESTRUCTIVE: it is session-atomic, so a session already present (matched by its UUID) is skipped whole together with its agents/events/token_usage/workflows, and independent config rows (dashboard_runs, alert_rules, model_pricing) are inserted only when absent. Nothing existing is overwritten — ideal for consolidating several machines into one dashboard. The response reports per-table counts.' operationId: importData requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: The export .json file. application/json: schema: type: object required: - path properties: path: type: string description: Absolute path (server-side) to an export .json file. `~` is expanded. example: path: /Users/you/ccam-export-2026-06-26.json responses: '200': description: Per-table restore counts content: application/json: schema: $ref: '#/components/schemas/ImportResponse' example: ok: true source: agent-monitor-export-2026-06-26.json format: ccam-export sessions_imported: 42 sessions_skipped: 3 agents: 96 events: 5120 token_usage: 42 workflows: 4 dashboard_runs: 11 alert_rules: 2 model_pricing: 0 errors: 0 '400': description: Missing file, invalid JSON, or unrecognized export format x-issues-url: https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor/issues