Files
Claude-Code-Monitor/openapi.yaml
T
nntrivi2001 4f84d2d7e2 feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 13:49:16 +07:00

9677 lines
370 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` = <cwd>/.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 <root>/skills/<name>/.'
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 <root>/<subdir>/<name>.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 `<name>@<marketplace>`.
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[<root>])`, 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[<root>].'
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 `<redacted>`.
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: <redacted>
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/<slug>/memory/.'
example: auto-memory
project:
type: string
description: 'Auto-memory only: the ~/.claude/projects/<slug> 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` (<cwd>/.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 <root>/skills/<name>/SKILL.md; `agents`/`commands`/`output-styles` write <root>/<type>/<name>.md; `memory` writes the scope's CLAUDE.md; `auto-memory` writes ~/.claude/projects/<project>/memory/<name>.
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/<slug> 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/<slug> 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/<slug> 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 `<homedir>/.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 1500 (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 `<session_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 1500 (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: <rule> }` (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 1200 (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 1200 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: <count> }` 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 1200 (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 1200).
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 (<root>/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 (<root>/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 (<root>/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[<root>]) 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 `<redacted>` (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: <redacted>
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/<slug>/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 1500).
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/{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 165535'
/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 165535'
'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 `<homedir>/.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 11000 (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": "<absolute 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