Default port: Desktop App 19280, ESP32 WiFi 80
| 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.
| 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) |
| 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 | - | ✓ |
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: trueis added when no state change is detected (optimization). If a project is blocked (project locked or max windows),successisfalsewith anerrorfield.
Response (ESP32 WiFi):
{"success": true}
If blocked by project lock:
{"success": false, "blocked": true}
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
}
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}}
]
}
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}
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 current window mode.
curl http://127.0.0.1:19280/window-mode
Response:
{"mode": "multi", "windowCount": 2, "lockedProject": null}
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}
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
idlestate and clearstool,model,memory.
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 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
}
windowModeis Desktop-only (ESP32 has no window mode concept).
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 (
lockedProjectbecomes null) and persists the new mode to Flash storage.
Serve the stats dashboard HTML page.
# Open in browser
open http://127.0.0.1:19280/stats
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"
}
Health check endpoint.
curl http://127.0.0.1:19280/health
Response:
{"status": "ok"}
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"
}
Quit the application.
curl -X POST http://127.0.0.1:19280/quit
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}
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:
wifiSSID, wifiPassword from NVS (WebSocket token is preserved)VibeMon-Setup AP)See ESP32 Setup Guide for details.
| 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
successfield in the response body to determine the outcome.
{"error": "Error message description"}