# ccboard Web API Documentation
This document describes the REST API and Server-Sent Events (SSE) interface provided by the ccboard web backend (Axum).
---
## Quick Start
**Start the backend**:
```bash
cargo run -- web --port 8080
```
**Base URL**: `http://localhost:8080`
**CORS**: Configured for local development (allows `http://localhost:3333`)
---
## Endpoints
### GET `/api/health`
Health check endpoint returning server status and basic metrics.
**Response** (200 OK):
```json
{
"status": "healthy",
"sessions": 1234,
"stats_loaded": true
}
```
**Fields**:
- `status` (string): `"healthy"` if all systems operational, `"degraded"` if issues detected
- `sessions` (integer): Total number of sessions loaded in memory
- `stats_loaded` (boolean): Whether stats-cache.json was successfully loaded
**Use Case**: Monitor backend health, check if data is loaded before making other API calls
**Example**:
```bash
curl http://localhost:8080/api/health | jq
```
---
### GET `/api/stats`
Returns global Claude Code statistics aggregated from `~/.claude/stats-cache.json`, enriched with analytics (forecast, daily activity, model breakdown).
**Response** (200 OK):
```json
{
"version": 2,
"lastComputedDate": "2026-02-10",
"firstSessionDate": "2025-12-10T09:45:00.350Z",
"totalSessions": 1757,
"totalMessages": 512937,
"thisMonthCost": 11205.38,
"avgSessionCost": 3.06,
"cacheHitRatio": 0.999,
"mcpServersCount": 3,
"mostUsedModel": "claude-opus-4-6",
"totalSpeculationTimeSavedMs": 0,
"longestSession": {
"sessionId": "d78b55ae-...",
"messageCount": 10827,
"date": null
},
"modelUsage": {
"claude-opus-4-6": {
"inputTokens": 5000,
"outputTokens": 7000,
"cacheCreationInputTokens": 300,
"cacheReadInputTokens": 45,
"costUsd": 123.45,
"contextWindow": 0,
"maxOutputTokens": 0,
"webSearchRequests": 0
}
},
"hourCounts": { "0": 6, "1": 2, "10": 133 },
"dailyActivity": [
{
"date": "2026-02-10",
"sessionCount": 42,
"messageCount": 12345,
"toolCallCount": 3456
}
],
"dailyModelTokens": [
{
"date": "2026-02-10",
"tokensByModel": { "claude-opus-4-6": 1332082 }
}
],
"dailyTokens30d": [66938374, 45000000],
"forecastTokens30d": [33071759, 40000000],
"forecastConfidence": 0.14,
"forecastCost30d": 9921.53,
"projectsByCost": [
{
"project": "/Users/john/code/myapp",
"cost": 4986.04,
"percentage": 44.5
}
]
}
```
**Top-Level Fields**:
- `version` (integer): Stats cache format version
- `lastComputedDate` (string): Date stats were last computed (YYYY-MM-DD)
- `firstSessionDate` (ISO 8601): Earliest session timestamp
- `totalSessions` (integer): Total number of sessions in stats cache
- `totalMessages` (integer): Total messages across all sessions
- `thisMonthCost` (float): Total cost in USD for the current month
- `avgSessionCost` (float): Average cost per session in USD
- `cacheHitRatio` (float): Cache read/creation ratio (0-1)
- `mcpServersCount` (integer): Number of configured MCP servers
- `mostUsedModel` (string|null): Most frequently used model name
- `totalSpeculationTimeSavedMs` (integer): Speculative execution time saved in ms
**Nested Objects**:
- `longestSession` (object): Session with most messages (`sessionId`, `messageCount`, `date`)
- `modelUsage` (object): Per-model token breakdown keyed by model ID, each containing `inputTokens`, `outputTokens`, `cacheCreationInputTokens`, `cacheReadInputTokens`, `costUsd`, `contextWindow`, `maxOutputTokens`, `webSearchRequests`
- `hourCounts` (object): Message count per hour (0-23), keyed as strings
**Arrays**:
- `dailyActivity` (array): Daily aggregates with `date`, `sessionCount`, `messageCount`, `toolCallCount`
- `dailyModelTokens` (array): Daily token usage per model with `date` and `tokensByModel` map
- `dailyTokens30d` (array): Daily total tokens for last 30 days (integers)
- `forecastTokens30d` (array): Predicted daily tokens for next 30 days (integers)
- `forecastConfidence` (float): Forecast confidence score (0-1)
- `forecastCost30d` (float): Predicted cost for next 30 days in USD
- `projectsByCost` (array): Top 5 projects by cost with `project`, `cost`, `percentage`
**Error Codes**:
- `500 Internal Server Error`: Stats cache failed to load
**Example**:
```bash
curl http://localhost:8080/api/stats | jq
```
---
### GET `/api/sessions/recent`
Returns the N most recent sessions across all projects (lightweight endpoint for dashboards).
**Query Parameters**:
- `limit` (integer, optional): Number of sessions to return (default: 5, max: 100)
**Response** (200 OK):
```json
{
"sessions": [
{
"id": "ea23759a-...",
"date": "2026-02-09T10:30:00Z",
"first_timestamp": "2026-02-09T10:00:00Z",
"project": "-Users-john-code-myapp",
"model": "claude-sonnet-4-5",
"messages": 42,
"tokens": 12345,
"input_tokens": 5000,
"output_tokens": 7000,
"cache_creation_tokens": 300,
"cache_read_tokens": 45,
"cost": 1.23,
"status": "completed",
"duration_seconds": null,
"preview": "How do I implement authentication?"
}
],
"total": 1234
}
```
**Fields**:
- `sessions` (array): Recent sessions sorted by date descending (same object format as `/api/sessions`)
- `total` (integer): Total number of sessions across all projects
**Example**:
```bash
curl "http://localhost:8080/api/sessions/recent?limit=10" | jq
```
---
### GET `/api/sessions/live`
Returns active Claude Code processes with real-time CPU and RAM monitoring.
**Response** (200 OK):
```json
{
"sessions": [
{
"pid": 12345,
"startTime": "2026-02-09T10:30:00+01:00",
"workingDirectory": "/Users/john/code/myapp",
"command": "claude",
"cpuPercent": 15.3,
"memoryMb": 512,
"tokens": 1234,
"sessionId": "ea23759a-...",
"sessionName": null
}
],
"total": 3
}
```
**Fields**:
- `pid` (integer): Process ID
- `startTime` (ISO 8601): Process start time (with timezone offset)
- `workingDirectory` (string): Current working directory of the process
- `command` (string): Process command name (e.g., `"claude"`)
- `cpuPercent` (float): Current CPU usage percentage
- `memoryMb` (integer): Current memory usage in MB
- `tokens` (integer): Tokens consumed in current session
- `sessionId` (string|null): Associated session UUID (matched from JSONL files)
- `sessionName` (string|null): Optional session name (if set by user)
- `total` (integer): Total number of active Claude processes
**Use Case**: Live monitoring dashboard with CPU/RAM badges
**Example**:
```bash
curl http://localhost:8080/api/sessions/live | jq
```
---
### GET `/api/sessions`
Returns session metadata with pagination, filtering, and sorting.
**Query Parameters**:
- `page` (integer, optional): Page number (default: 0)
- `limit` (integer, optional): Page size (default: 50, max: 100)
- `search` (string, optional): Search in session ID, project path, or first message
- `project` (string, optional): Filter by project path (partial match)
- `model` (string, optional): Filter by model name (partial match)
- `since` (string, optional): Filter by time range (e.g., `7d`, `30d`, `1h`)
- `sort` (string, optional): Sort field (`date`, `tokens`, `cost`) (default: `date`)
- `order` (string, optional): Sort order (`asc`, `desc`) (default: `desc`)
**Response** (200 OK):
```json
{
"sessions": [
{
"id": "ea23759a-1234-5678-90ab-cdef01234567",
"date": "2026-02-09T10:30:00Z",
"project": "-Users-john-code-myapp",
"model": "claude-sonnet-4-5",
"messages": 42,
"tokens": 12345,
"input_tokens": 5000,
"output_tokens": 7000,
"cache_creation_tokens": 300,
"cache_read_tokens": 45,
"cost": 1.23,
"status": "completed",
"first_timestamp": "2026-02-09T10:00:00Z",
"duration_seconds": 1800,
"preview": "How do I implement authentication?"
}
],
"total": 1234,
"page": 0,
"page_size": 50
}
```
**Response Fields**:
- `sessions` (array): Array of session objects
- `total` (integer): Total number of sessions matching filters (before pagination)
- `page` (integer): Current page number
- `page_size` (integer): Number of sessions per page
**Session Object Fields**:
- `id` (string): Session UUID
- `date` (ISO 8601): Last message timestamp
- `project` (string): Project path
- `model` (string): Primary model used (first in list)
- `messages` (integer): Number of messages in session
- `tokens` (integer): Total tokens (input + output + cache)
- `input_tokens` (integer): Input tokens consumed
- `output_tokens` (integer): Output tokens generated
- `cache_creation_tokens` (integer): Tokens written to cache
- `cache_read_tokens` (integer): Tokens read from cache
- `cost` (float): Estimated cost in USD
- `first_timestamp` (ISO 8601): Session start time
- `duration_seconds` (integer|null): Session duration (null if not computed)
- `preview` (string): First user message (truncated to ~200 chars)
**Error Codes**:
- `500 Internal Server Error`: Failed to load sessions from SQLite cache
**Examples**:
```bash
# Get recent sessions for specific project
curl "http://localhost:8080/api/sessions?project=myapp&limit=10" | jq
# Search sessions containing "auth"
curl "http://localhost:8080/api/sessions?search=auth" | jq
# Get sessions from last 7 days, sorted by cost
curl "http://localhost:8080/api/sessions?since=7d&sort=cost&order=desc" | jq
# Filter by model and paginate
curl "http://localhost:8080/api/sessions?model=opus&page=1&limit=20" | jq
```
---
### GET `/api/config/merged`
Returns merged configuration from global, project, and local settings.
**Response** (200 OK):
```json
{
"global": {
"model": "claude-sonnet-4-5",
"temperature": 0.7
},
"project": {
"temperature": 0.5
},
"local": {
"max_tokens": 4096
},
"merged": {
"model": "claude-sonnet-4-5",
"temperature": 0.5,
"max_tokens": 4096
}
}
```
**Merge Priority**: `local` > `project` > `global` > defaults
**Fields**:
- `global` (object): Settings from `~/.claude/settings.json`
- `project` (object): Settings from `.claude/settings.json`
- `local` (object): Settings from `.claude/settings.local.json`
- `merged` (object): Final merged configuration (highest priority wins)
**Error Codes**:
- `500 Internal Server Error`: Failed to parse configuration files
**Example**:
```bash
curl http://localhost:8080/api/config/merged | jq
```
---
### GET `/api/hooks`
Returns all configured hooks from merged settings (global + project + local) with script content.
**Response** (200 OK):
```json
{
"hooks": [
{
"name": "UserPromptSubmit",
"event": "UserPromptSubmit",
"command": "current_model=$(jq -r ...) ...",
"description": "current_model=$(jq -r ...) ...",
"async": false,
"timeout": null,
"cwd": null,
"matcher": null,
"scriptPath": null,
"scriptContent": null
},
{
"name": "PreToolUse-0-0",
"event": "PreToolUse",
"command": "case \"$TOOL_INPUT\" in ...",
"description": "case \"$TOOL_INPUT\" in ...",
"async": false,
"timeout": null,
"cwd": null,
"matcher": "Bash",
"scriptPath": null,
"scriptContent": null
}
],
"total": 5
}
```
**Fields**:
- `name` (string): Hook identifier (event name or `event-group-index` for multiple hooks per event)
- `event` (string): Event trigger (`UserPromptSubmit`, `PreToolUse`, `Custom`, etc.)
- `command` (string): Inline command or script path
- `description` (string): Extracted from `# Description:` comment in script, or command itself
- `async` (boolean): Whether hook runs asynchronously
- `timeout` (integer|null): Timeout in seconds (null if not set)
- `cwd` (string|null): Working directory override
- `matcher` (string|null): Tool matcher pattern (e.g., `"Bash"` for PreToolUse hooks)
- `scriptPath` (string|null): Path to external script file (if command references a `.sh` file)
- `scriptContent` (string|null): Full script content (loaded when scriptPath is set)
- `total` (integer): Total number of hooks
**Use Case**: Hooks tab in TUI/Web, syntax highlighting for bash scripts
**Example**:
```bash
curl http://localhost:8080/api/hooks | jq
```
---
### GET `/api/mcp`
Returns MCP server configuration from `claude_desktop_config.json`.
**Response** (200 OK):
```json
{
"servers": [
{
"name": "filesystem",
"command": "npx -y @modelcontextprotocol/server-filesystem /Users/john",
"serverType": "stdio",
"url": null,
"args": ["/Users/john"],
"env": {},
"hasEnv": false
},
{
"name": "brave-search",
"command": null,
"serverType": "http",
"url": "http://localhost:3100/sse",
"args": [],
"env": {"BRAVE_API_KEY": "..."},
"hasEnv": true
}
],
"total": 2
}
```
**Fields**:
- `name` (string): Server identifier
- `command` (string): Command for stdio servers
- `serverType` (string): `"stdio"` or `"http"`
- `url` (string): URL for HTTP servers
- `args` (array): Command arguments
- `env` (object): Environment variables
- `hasEnv` (boolean): Whether server has environment variables
**Use Case**: MCP tab in TUI/Web, server status monitoring
**Example**:
```bash
curl http://localhost:8080/api/mcp | jq
```
---
### GET `/api/agents`
Returns agents from `~/.claude/agents/` with frontmatter metadata.
**Response** (200 OK):
```json
{
"items": [
{
"name": "backend-architect",
"frontmatter": {
"title": "Backend Architect",
"version": "1.0.0",
"description": "Expert backend developer"
},
"body": "# Backend Architect\n\nExpert senior en architecture backend...",
"path": "/Users/john/.claude/agents/backend-architect.md"
}
],
"total": 1
}
```
**Fields**:
- `name` (string): Agent filename (without `.md`)
- `frontmatter` (object): YAML metadata between `---` markers (empty `{}` if no frontmatter)
- `body` (string): Markdown content after frontmatter
- `path` (string): Full file path
- `total` (integer): Total number of items
**Use Case**: Agents/Capabilities tab in TUI/Web
**Example**:
```bash
curl http://localhost:8080/api/agents | jq
```
---
### GET `/api/commands`
Returns commands from `~/.claude/commands/` with frontmatter metadata.
**Response**: Same format as `/api/agents`
**Example**:
```bash
curl http://localhost:8080/api/commands | jq
```
---
### GET `/api/skills`
Returns skills from `~/.claude/skills/*/SKILL.md` with frontmatter metadata.
**Response** (200 OK):
```json
{
"items": [
{
"name": "ccboard",
"frontmatter": {
"name": "ccboard",
"invoke": "ccboard",
"version": "0.5.0"
},
"body": "# ccboard Skill\n\nComprehensive TUI/Web dashboard...",
"path": "/Users/john/.claude/skills/ccboard/SKILL.md"
}
],
"total": 1
}
```
**Fields**: Same as `/api/agents` and `/api/commands`
**Note**: Skills are scanned from subdirectories, looking for `SKILL.md` files
**Example**:
```bash
curl http://localhost:8080/api/skills | jq
```
---
### GET `/api/plugins`
Returns plugin usage analytics aggregated across the last 10 000 sessions — invocation counts, token consumption, and dead-code detection for skills and commands.
**Response** (200 OK):
```json
{
"analytics": {
"skill_usage": [
{ "name": "ccboard", "invocations": 42, "tokens": 12345, "last_used": "2026-03-28T14:00:00Z" }
],
"command_usage": [
{ "name": "commit", "invocations": 15, "tokens": 4500, "last_used": "2026-03-29T10:00:00Z" }
],
"dead_skills": ["unused-skill"],
"dead_commands": []
},
"generated_at": "2026-03-30T09:00:00Z"
}
```
**Use Case**: Plugins tab — usage analytics, dead-code detection, sort by usage/cost/name
**Example**:
```bash
curl http://localhost:8080/api/plugins | jq
```
---
### GET `/api/analytics/suggestions`
Returns actionable cost-optimization suggestions based on dead plugins and high-cost tools.
**Response** (200 OK):
```json
{
"suggestions": [
{
"type": "dead_plugin",
"plugin": "unused-skill",
"message": "Skill 'unused-skill' has 0 invocations. Consider removing it.",
"potential_saving_usd": null
},
{
"type": "high_cost_tool",
"tool": "Bash",
"tokens": 782000,
"pct_of_total": 34.2,
"message": "Bash accounts for 34% of total tokens. Consider batching commands.",
"potential_saving_usd": 12.50
}
],
"generated_at": "2026-03-30T09:00:00Z"
}
```
**Use Case**: Analytics > Discover sub-view
**Example**:
```bash
curl http://localhost:8080/api/analytics/suggestions | jq
```
---
### GET `/api/quota`
Returns current budget and quota status. Returns an error object if no budget is configured.
**Response** (200 OK):
```json
{
"current_cost": 38.42,
"budget_limit": 50.0,
"usage_pct": 76.8,
"projected_monthly_cost": 61.5,
"projected_overage": 11.5,
"alert_level": "warning"
}
```
**Fields**:
- `current_cost` (float): Month-to-date cost in USD
- `budget_limit` (float): Configured monthly budget in USD
- `usage_pct` (float): Percentage of budget consumed (0–100+)
- `projected_monthly_cost` (float): Forecasted end-of-month cost
- `projected_overage` (float): Forecasted overage vs budget (0 if under budget)
- `alert_level` (string): `"safe"` / `"warning"` / `"critical"` / `"exceeded"`
**Error Response** (when no budget configured):
```json
{ "error": "No budget configured or stats not loaded" }
```
**Configuration**: Set `budget.monthlyBudgetUsd` in `~/.claude/settings.json` to enable.
**Example**:
```bash
curl http://localhost:8080/api/quota | jq
```
---
### GET `/api/search`
Full-text search across all session content using SQLite FTS5.
**Query Parameters**:
- `q` (string, required): Search query (minimum 2 characters)
- `limit` (integer, optional): Maximum results to return (default: 50)
**Response** (200 OK):
```json
{
"results": [
{
"session_id": "ea23759a-...",
"path": "/Users/john/.claude/projects/-Users-john-code-myapp/ea23759a.jsonl",
"project": "-Users-john-code-myapp",
"first_user_message": "How do I implement authentication?",
"snippet": "...implement authentication with JWT tokens...",
"rank": 0.95
}
],
"total": 3,
"query": "authentication"
}
```
**Fields**:
- `results` (array): Search results ranked by BM25 relevance
- `snippet` (string): Highlighted excerpt with `` tags around matches
- `rank` (float): Relevance score (higher = more relevant)
- `total` (integer): Number of results returned
**Example**:
```bash
curl "http://localhost:8080/api/search?q=authentication&limit=10" | jq
```
---
### GET `/api/activity/violations`
Returns cross-session security violations feed (credential access, destructive commands).
**Query Parameters**:
- `min_severity` (string, optional): Minimum severity to return — `"Info"` (default, all), `"Warning"`, `"Critical"`
- `limit` (integer, optional): Maximum number of violations to return (default: 100)
**Response** (200 OK):
```json
{
"violations": [
{
"session_id": "ea23759a-...",
"timestamp": "2026-03-28T14:32:00Z",
"severity": "Warning",
"category": "CredentialAccess",
"detail": "Read ~/.aws/credentials",
"action_hint": "Verify no secrets were exposed; rotate credentials if in doubt"
}
],
"total": 12,
"displayed": 12,
"critical_count": 2,
"warning_count": 7,
"info_count": 3
}
```
**Severity levels**: `"Info"` → `"Warning"` → `"Critical"`
**Example**:
```bash
curl "http://localhost:8080/api/activity/violations?min_severity=Warning" | jq
```
---
### GET `/api/activity/{session_id}`
On-demand security analysis of a single session's tool calls. Results are cached in SQLite after the first call.
**Path Parameters**:
- `session_id` (string): Session UUID
**Response** (200 OK):
```json
{
"session_id": "ea23759a-...",
"file_accesses": [
{ "path": "~/.aws/credentials", "operation": "Read" }
],
"bash_commands": [
{ "command": "rm -rf /tmp/foo", "risk_level": "Low" }
],
"network_calls": [],
"alerts": [
{
"severity": "Warning",
"category": "CredentialAccess",
"detail": "Read ~/.aws/credentials",
"action_hint": "Verify no secrets were exposed"
}
]
}
```
**Example**:
```bash
curl http://localhost:8080/api/activity/ea23759a-1234-5678-90ab-cdef01234567 | jq
```
---
### GET `/api/task-graph`
Returns the task dependency graph parsed from a project `PLAN.md` file. Searches `claudedocs/PLAN.md`, `.claude/PLAN.md`, and `~/.claude/claudedocs/PLAN.md` in order.
**Response** (200 OK, plan found):
```json
{
"found": true,
"plan_path": "/Users/john/code/myapp/claudedocs/PLAN.md",
"phases": [...],
"graph": {
"nodes": [...],
"edges": [...]
}
}
```
**Response** (200 OK, no plan found):
```json
{ "found": false, "plan_path": null }
```
**Example**:
```bash
curl http://localhost:8080/api/task-graph | jq
```
---
### GET `/api/claude-mem/summaries`
Returns session summaries stored by the claude-mem integration (if enabled).
**Response** (200 OK):
```json
{
"enabled": true,
"summaries": [
{
"id": 1,
"memory_session_id": "abc123",
"project": "/Users/john/code/myapp",
"request": "Implement user authentication",
"completed": "Added JWT middleware and login/logout routes",
"next_steps": "Add refresh token rotation",
"files_edited": ["src/auth.rs", "src/routes.rs"],
"created_at": "2026-03-28T14:00:00Z"
}
],
"total": 1
}
```
**Example**:
```bash
curl http://localhost:8080/api/claude-mem/summaries | jq
```
---
### POST `/api/claude-mem/toggle`
Enable or disable the claude-mem integration at runtime.
**Request Body**:
```json
{ "enabled": true }
```
**Response** (200 OK):
```json
{ "enabled": true }
```
**Example**:
```bash
curl -X POST http://localhost:8080/api/claude-mem/toggle \
-H "Content-Type: application/json" \
-d '{"enabled": true}' | jq
```
---
### GET `/api/insights`
Returns insights from `~/.ccboard/insights.db` — the cross-session knowledge base populated by the session-stop hook and `/ccboard-remember` skill.
**Query Parameters**:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `project` | string | — | Filter by project path (exact match) |
| `type` | string | — | Filter by insight type: `progress`, `decision`, `blocked`, `pattern`, `fix`, `context` |
| `limit` | integer | 50 | Maximum number of results |
| `archived` | integer | 0 | Include archived insights (`1`) or not (`0`) |
**Response** (200 OK):
```json
{
"insights": [
{
"id": 1,
"session_id": "abc123",
"project": "/Users/you/Sites/myproject",
"type": "progress",
"content": "Implemented Brain tab with filter bar and detail pane",
"reasoning": null,
"archived": false,
"created_at": "2026-03-30T06:46:52Z"
}
],
"total": 1
}
```
**Use Case**: Brain tab — cross-session knowledge base
**Example**:
```bash
curl "http://localhost:8080/api/insights?type=blocked&limit=10" | jq
```
---
### GET `/api/events` (Server-Sent Events)
Live update stream for real-time monitoring. Pushes events when `~/.claude` files change.
**Event Types**:
- `stats_updated`: Global stats changed (e.g., `stats-cache.json` modified)
- `session_created`: New session detected (e.g., new `.jsonl` file)
- `session_updated`: Session file modified (e.g., message added)
- `config_changed`: Configuration file changed (e.g., `settings.json` modified)
**Response** (SSE stream):
```
event: stats_updated
data: {"total_sessions": 1235, "total_tokens": 45680000}
event: session_created
data: {"id": "fb34860b-...", "project": "-Users-john-code-myapp"}
event: session_updated
data: {"id": "ea23759a-...", "message_count": 43}
event: config_changed
data: {"scope": "global", "key": "model", "value": "claude-opus-4-6"}
```
**Usage (JavaScript)**:
```javascript
const events = new EventSource('http://localhost:8080/api/events');
events.addEventListener('stats_updated', (e) => {
const data = JSON.parse(e.data);
console.log('Stats updated:', data);
});
events.addEventListener('session_created', (e) => {
const data = JSON.parse(e.data);
console.log('New session:', data.id);
});
events.onerror = (err) => {
console.error('SSE error:', err);
events.close();
};
```
**Connection Management**:
- Server sends keepalive every 30 seconds (`: keepalive` comment)
- Client should reconnect on error (automatic in most SSE libraries)
- Server closes connection after 5 minutes idle (no events)
**Error Codes**:
- `500 Internal Server Error`: EventBus subscription failed
---
## CORS Configuration
The API is configured for local development with CORS enabled:
**Allowed Origins**:
- `http://localhost:3333` (Leptos frontend via Trunk)
- `http://127.0.0.1:3333` (alternative localhost)
**Allowed Methods**:
- `GET`, `POST`, `OPTIONS`
**Allowed Headers**:
- `Content-Type`, `Authorization`
**Production Note**: Update CORS origins in `ccboard-web/src/router.rs` before deploying to production.
---
## Error Handling
All endpoints return JSON errors with this format:
```json
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": "Optional additional context"
}
```
**Common Error Codes**:
- `STATS_LOAD_FAILED`: Failed to load stats-cache.json
- `SESSIONS_LOAD_FAILED`: Failed to query SQLite cache
- `CONFIG_PARSE_FAILED`: Failed to parse settings.json
- `PROJECT_NOT_FOUND`: Specified project does not exist
- `INVALID_PARAMETER`: Missing or invalid query parameter
---
## Performance Considerations
### Caching
- **Stats**: Cached in memory, invalidated on `stats-cache.json` change
- **Sessions**: Stored in SQLite cache (89x faster than JSONL parsing)
- **Config**: Cached in memory, invalidated on `settings.json` change
### Rate Limiting
Currently **no rate limiting** (local development only). Production deployment should add:
- Token bucket rate limiting (e.g., 100 requests/minute per IP)
- Request size limits (max 1MB for POST bodies)
### Scalability
- **Single-instance**: Designed for local `~/.claude` monitoring (1 user)
- **Multi-user**: Not currently supported (local-only design)
---
## Client SDKs
### JavaScript/TypeScript
```typescript
// stats.ts
export async function getStats() {
const response = await fetch('http://localhost:8080/api/stats');
if (!response.ok) throw new Error('Failed to fetch stats');
return response.json();
}
// sessions.ts
export async function getSessions(project: string) {
const url = `http://localhost:8080/api/sessions?project=${encodeURIComponent(project)}`;
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch sessions');
return response.json();
}
// events.ts
export function subscribeToEvents(handlers: {
onStatsUpdated?: (data: any) => void;
onSessionCreated?: (data: any) => void;
}) {
const events = new EventSource('http://localhost:8080/api/events');
if (handlers.onStatsUpdated) {
events.addEventListener('stats_updated', (e) => {
handlers.onStatsUpdated(JSON.parse(e.data));
});
}
if (handlers.onSessionCreated) {
events.addEventListener('session_created', (e) => {
handlers.onSessionCreated(JSON.parse(e.data));
});
}
return events;
}
```
### Rust
```rust
// Using reqwest for HTTP client
use reqwest::Client;
use serde_json::Value;
async fn get_stats(client: &Client) -> Result {
let response = client
.get("http://localhost:8080/api/stats")
.send()
.await?;
response.json().await
}
async fn get_sessions(client: &Client, project: &str) -> Result, reqwest::Error> {
let url = format!("http://localhost:8080/api/sessions?project={}", project);
let response = client.get(&url).send().await?;
response.json().await
}
```
---
## Testing
### Manual Testing
```bash
# Start backend
cargo run -- web --port 8080
# Test stats endpoint
curl http://localhost:8080/api/stats | jq
# Test sessions endpoint
curl "http://localhost:8080/api/sessions?project=-Users-john-code-myapp" | jq
# Test config endpoint
curl http://localhost:8080/api/config/merged | jq
# Test SSE (leave running)
curl -N http://localhost:8080/api/events
```
### Automated Testing
```bash
# Integration tests (requires real ~/.claude data)
cargo test --test api_integration
# Load testing with wrk
wrk -t4 -c100 -d30s http://localhost:8080/api/stats
```
---
## Troubleshooting
### "Failed to load stats"
**Cause**: `~/.claude/stats-cache.json` missing or corrupted
**Solution**: Run Claude Code once to generate stats cache
---
### "Connection refused"
**Cause**: Backend not running or wrong port
**Solution**: Verify backend is running with `lsof -i :8080`
---
### SSE connection drops
**Cause**: Server closes connection after 5 minutes idle
**Solution**: Client should automatically reconnect (built into EventSource)
---
### CORS errors in browser
**Cause**: Frontend running on non-allowed origin
**Solution**: Add origin to CORS config in `ccboard-web/src/router.rs`
---
## Future API Additions
Planned endpoints:
- `GET /api/conversation/:session_id` — Full JSONL session content for the conversation viewer
---
**Last Updated**: 2026-03-30
**API Version**: v0.21.0
**Backend**: Axum + Tokio
**Frontend**: Leptos + WASM