vibemon-app

HTTP API Reference

Default port: Desktop App 19280, ESP32 WiFi 80

Security & Limits (Desktop only)

Limit Value Description
Payload size 10KB Maximum request body size
Rate limit 100 req/min Per IP address
Request timeout 30 sec Prevents Slowloris attacks
CORS localhost only Only allows localhost origins

Note: ESP32 HTTP server does not enforce these limits. ESP32 security relies on local network isolation and SSID sanitization.

Input Validation

Field Max Length Format
state - One of valid states
project 100 chars String
tool 50 chars String
model 50 chars String
memory - Integer 0-100
character - apto, clawd, kiro, or claw
terminalId 100 chars Desktop only. Terminal session ID with prefix: iterm2:w0t0p0:UUID (from ITERM_SESSION_ID) or ghostty:12345 (from GHOSTTY_PID)

Platform Support

Endpoint Desktop ESP32 WiFi
POST/GET /status
GET /windows -
POST /close -
POST /show -
GET /health
GET /debug -
POST /quit -
POST /lock
POST /unlock
GET/POST /lock-mode
GET/POST /window-mode -
GET /stats -
GET /stats/data -
POST /reboot -
POST /wifi-reset -

Status

POST /status

Update monitor status.

curl -X POST http://127.0.0.1:19280/status \
  -H "Content-Type: application/json" \
  -d '{"state":"working","tool":"Bash","project":"my-project"}'

Request Body:

Field Type Description
state string start, idle, thinking, planning, working, packing, notification, done, sleep, alert
tool string Tool name (e.g., Bash, Read, Edit)
project string Project name
model string Model name (e.g., opus, sonnet)
memory number Memory usage (0-100)
character string apto, clawd, kiro, or claw
terminalId string Desktop only. Terminal ID for click-to-focus (e.g., iterm2:w0t0p0:UUID or ghostty:12345)

Response (Desktop):

{"success": true, "project": "my-project", "state": "working", "windowCount": 2}

skipped: true is added when no state change is detected (optimization). If a project is blocked (project locked or max windows), success is false with an error field.

Response (ESP32 WiFi):

{"success": true}

If blocked by project lock: {"success": false, "blocked": true}

GET /status

Get current status.

curl http://127.0.0.1:19280/status

Response (Desktop):

{
  "windowCount": 2,
  "projects": {
    "my-project": {"state": "working", "tool": "Bash", "model": "opus", "memory": 45},
    "other-project": {"state": "idle"}
  }
}

Response (ESP32 WiFi):

{
  "state": "working",
  "project": "my-project",
  "lockedProject": "my-project",
  "lockMode": "on-thinking",
  "projectCount": 1
}

GET /windows

List all active windows with their states and positions.

curl http://127.0.0.1:19280/windows

Response:

{
  "windowCount": 2,
  "windows": [
    {"project": "my-project", "state": "working", "bounds": {"x": 1748, "y": 23, "width": 172, "height": 348}},
    {"project": "other-project", "state": "idle", "bounds": {"x": 1566, "y": 23, "width": 172, "height": 348}}
  ]
}

Window Management

POST /close

Close a specific project window.

curl -X POST http://127.0.0.1:19280/close \
  -H "Content-Type: application/json" \
  -d '{"project":"my-project"}'

Response:

{"success": true, "project": "my-project", "windowCount": 1}

POST /show (Desktop only)

Show window and position to top-right corner.

# Show first window
curl -X POST http://127.0.0.1:19280/show

# Show specific project window
curl -X POST http://127.0.0.1:19280/show \
  -H "Content-Type: application/json" \
  -d '{"project":"my-project"}'

Request Body (optional):

Field Type Description
project string Project name to show (defaults to first window)

Response:

{"success": true, "project": "my-project"}

GET /window-mode (Desktop only)

Get current window mode.

curl http://127.0.0.1:19280/window-mode

Response:

{"mode": "multi", "windowCount": 2, "lockedProject": null}

POST /window-mode (Desktop only)

Set window mode (multi or single).

curl -X POST http://127.0.0.1:19280/window-mode \
  -H "Content-Type: application/json" \
  -d '{"mode":"single"}'

Response:

{"success": true, "mode": "single", "windowCount": 1, "lockedProject": null}

Project Lock

POST /lock

Lock to a specific project.

# Desktop
curl -X POST http://127.0.0.1:19280/lock \
  -H "Content-Type: application/json" \
  -d '{"project":"my-project"}'

# ESP32
curl -X POST http://192.168.0.185/lock \
  -H "Content-Type: application/json" \
  -d '{"project":"my-project"}'

Request Body:

Field Type Description
project string Project name to lock. Defaults to current project if omitted (ESP32 only).

Response:

{"success": true, "lockedProject": "my-project"}

Desktop: Only works in single-window mode. Returns {"success": false, "error": "Lock only available in single-window mode"} in multi-window mode.

ESP32: Always available. When locking a new project, the display transitions to idle state and clears tool, model, memory.

POST /unlock

Unlock project.

# Desktop
curl -X POST http://127.0.0.1:19280/unlock

# ESP32
curl -X POST http://192.168.0.185/unlock

Response:

{"success": true, "lockedProject": null}

Desktop: Only works in single-window mode. Returns {"success": false, "error": "Unlock only available in single-window mode"} in multi-window mode.

GET /lock-mode

Get current lock mode.

# Desktop
curl http://127.0.0.1:19280/lock-mode

# ESP32
curl http://192.168.0.185/lock-mode

Response (Desktop):

{
  "mode": "on-thinking",
  "modes": {"first-project": "First Project", "on-thinking": "On Thinking"},
  "lockedProject": null,
  "windowMode": "single"
}

Response (ESP32 WiFi):

{
  "mode": "on-thinking",
  "modes": {"first-project": "First Project", "on-thinking": "On Thinking"},
  "lockedProject": null
}

windowMode is Desktop-only (ESP32 has no window mode concept).

POST /lock-mode

Set lock mode (first-project or on-thinking).

# Desktop
curl -X POST http://127.0.0.1:19280/lock-mode \
  -H "Content-Type: application/json" \
  -d '{"mode":"first-project"}'

# ESP32
curl -X POST http://192.168.0.185/lock-mode \
  -H "Content-Type: application/json" \
  -d '{"mode":"first-project"}'

Response:

{"success": true, "mode": "first-project", "lockedProject": null}

ESP32: Changing lock mode resets the current lock (lockedProject becomes null) and persists the new mode to Flash storage.


Statistics (Desktop only)

GET /stats

Serve the stats dashboard HTML page.

# Open in browser
open http://127.0.0.1:19280/stats

GET /stats/data

Get stats data from ~/.claude/stats-cache.json.

curl http://127.0.0.1:19280/stats/data

Response:

{
  "sessions": [...],
  "totalTokens": 12345,
  "lastUpdated": "2026-01-29T12:00:00Z"
}

System

GET /health

Health check endpoint.

curl http://127.0.0.1:19280/health

Response:

{"status": "ok"}

GET /debug (Desktop only)

Get display and window debug information.

curl http://127.0.0.1:19280/debug

Response:

{
  "primaryDisplay": {"bounds": {"x": 0, "y": 0, "width": 1920, "height": 1080}, "workArea": {...}},
  "allDisplays": [...],
  "windows": [{"project": "my-project", "bounds": {...}, "isVisible": true}],
  "windowCount": 1,
  "maxWindows": 5,
  "alwaysOnTopMode": "active-only",
  "platform": "darwin"
}

POST /quit (Desktop only)

Quit the application.

curl -X POST http://127.0.0.1:19280/quit

POST /reboot (ESP32 only)

Reboot the ESP32 device.

curl -X POST http://192.168.0.185/reboot \
  -H "Content-Type: application/json" \
  -d '{"confirm":true}'

Response:

{"success": true, "rebooting": true}

POST /wifi-reset (ESP32 only)

Clear saved WiFi credentials and return to provisioning mode.

curl -X POST http://192.168.0.185/wifi-reset \
  -H "Content-Type: application/json" \
  -d '{"confirm":true}'

Response:

{"success": true, "message": "WiFi credentials cleared. Rebooting..."}

Behavior:

See ESP32 Setup Guide for details.


HTTP Status Codes

Code Desktop ESP32
200 Success Success (also used for some errors — check success field)
400 Bad request (validation error) Bad request (missing body or invalid input)
404 Not found -
408 Request timeout -
413 Payload too large (>10KB) -
429 Too many requests (rate limited) -
500 Internal server error -

ESP32 note: The ESP32 HTTP server always returns HTTP 200 for valid requests (including project-lock rejections). Check the success field in the response body to determine the outcome.

Error Response Format

{"error": "Error message description"}