vibemon-app

HTTP API Reference

This reference describes the Desktop App’s local HTTP server on 127.0.0.1:19280. Cloud ingestion, token management, and trends use the separate Web API.

For the ESP32 device API, see vibemon-esp32.

Security & Limits

Limit Value Description
Payload size 10KB Maximum request body size
Rate limit 600 req/min Per IP address
Request timeout 30 sec Prevents Slowloris attacks
Browser origin localhost only Requests with a non-local browser Origin are rejected
Request host loopback only A supplied Host must name localhost, 127.0.0.1, or [::1], with an optional port
Content type application/json Required for JSON mutation endpoints

Input Validation

Field Max Length Format
state - One of valid states
project 128 chars String
tool 64 chars String
model 64 chars String
memory - Finite number 0-100 (context-window usage)
usage5h - Finite number 0-100 (5-hour plan-usage window)
usageWeek - Finite number 0-100 (weekly plan-usage window)
usage5hResetsIn - Non-negative integer (minutes until the 5-hour window resets)
usageWeekResetsIn - Non-negative integer (minutes until the weekly window resets)
usageWeekModel - Finite number 0-100 (model-scoped weekly plan-usage window, e.g. the Fable bucket)
usageWeekModelResetsIn - Non-negative integer (minutes until the model-scoped weekly window resets)
usageWeekModelLabel 64 chars Display label for the model-scoped weekly window (e.g. Fable)
usageObservedAt - Optional UTC timestamp, no later than the report: when its plan usage was measured. Web resolves each account’s usage by this time. Mac widgets use that cloud result when available; local-only accounts compare this time with the local cache. Without it, a non-heartbeat report uses observedAt, or its first receipt time when both are absent. A heartbeat does not make an older measurement current.
character - vibemon, clawd, codex, kiro, claw, or daangni (unknown names fall back to vibemon)
terminalId 100 chars Terminal session ID with prefix: iterm2:w0t0p0:UUID (from ITERM_SESSION_ID) or ghostty:12345 (from GHOSTTY_PID)

character is a visual rendering choice, typically selected by the agent bridge. It is not a general agent identity field.


Endpoints

Endpoint Description
GET / Local API identity and desktop workspace guidance
GET /dashboard-data Dashboard data (focused project, character lock, tracked projects)
POST/GET /status Update / get status
POST /close Close the character window
POST /show Show the character window
GET /health Health check
GET /debug Window/display debug info
POST /quit Quit application
GET/POST /character-lock Get / set character lock

Status

POST /status

Update a project’s status. The character window follows one “focused” project at a time: a project in an active state (thinking, planning, working, packing, notification, alert) takes focus; otherwise the most recently updated project keeps it. Updates for unfocused projects are still recorded and become visible when that project gains focus.

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
sourceId string Optional stable source identity, using the VibeMon source-ID rules
account object Optional { provider, id, displayName }, with the same identity rules as VibeMon
observedAt string Optional UTC event time; at most 120 seconds old or 30 seconds ahead
heartbeat boolean Optional. true repeats a running turn’s current state. It refreshes the state timeout but does not take focus from another project.
model string Model name (e.g., opus, sonnet)
memory number Context-window usage (0-100)
usage5h number 5-hour plan-usage window (0-100)
usageWeek number Weekly plan-usage window (0-100)
usageObservedAt string Optional UTC measurement time, no later than the report. It is separate from the activity time in observedAt.
usage5hResetsIn number Minutes until the 5-hour window resets
usageWeekResetsIn number Minutes until the weekly window resets
usageWeekModel number Model-scoped weekly plan-usage window (0-100), e.g. the Fable bucket
usageWeekModelResetsIn number Minutes until the model-scoped weekly window resets
usageWeekModelLabel string Display label for the model-scoped weekly window (e.g. Fable)
character string Character name. Registered names: vibemon, clawd, codex, kiro, claw, daangni. Unregistered names (including opencode) are accepted and normalized to vibemon; they are not Character Lock options.
terminalId string Terminal ID for click-to-focus (e.g., iterm2:w0t0p0:UUID or ghostty:12345)

An unrecognized state value is rejected with a 400 error.

Agent bridges usually set character automatically:

Unknown character names are accepted and normalized to vibemon; they do not reject the status update or become Character Lock options. OpenCode uses the same POST /status endpoint and cloud relay payload as the other bridges. Its adapter omits context and plan-usage fields because it does not collect usage metrics.

Current hooks send the same sourceId, account metadata and event timestamp to Desktop and VibeMon. Desktop keys these local events as local:<sourceId>; cloud observations use cloud:<sourceId>. Project labels remain unchanged. Accounts and workspaces with the same project name stay separate. Untimestamped legacy payloads without a source ID continue to use the project name as their key.

For a timestamped source, duplicate or older events return skipped: true with reason: "duplicate_or_older". They do not replace state or refresh timers. A source ID cannot move between accounts. GET /status exposes the resulting project keys; use those keys with /close and /show.

Response:

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

skipped: true is added when the update didn’t change the visible window — either because neither state nor the info fields (tool, model, memory, usage5h, usageWeek, usage5hResetsIn, usageWeekResetsIn, usageWeekModel, usageWeekModelResetsIn, usageWeekModelLabel, character, terminalId) changed, or because another project currently holds focus (the update is still recorded).

GET /status

Get every tracked project’s latest state, plus which one the character window currently follows.

curl http://127.0.0.1:19280/status

Response:

{
  "focusedProject": "my-project",
  "projects": {
    "my-project": {"state": "working", "tool": "Bash", "model": "opus", "memory": 45, "usage5h": 36, "usageWeek": 37},
    "other-project": {"state": "idle"}
  }
}

Window Management

POST /close

Close the character window (only succeeds when it currently follows the given project). It reappears on the next status update. Local activity keeps its normal idle/sleep timeouts while the companion is closed; those timers do not reopen it.

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"}

POST /show

Show an existing character window without taking keyboard focus from the active application. Use Show companion in the App to recreate a closed window. While the companion is hidden, no window exists and the response has success: false.

# Show the window regardless of which project it follows
curl -X POST http://127.0.0.1:19280/show

# Show only if it follows a specific project
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 the window must follow (defaults to whichever it follows)

Response:

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

GET /character-lock

Get current character lock.

curl http://127.0.0.1:19280/character-lock

Response:

{"character": "auto"}

POST /character-lock

Force the window to show one character regardless of what each project’s status reports (auto, or one of vibemon, clawd, codex, kiro, claw, daangni). auto immediately restores the focused project’s latest reported character. The project registry retains each tool’s reported character.

curl -X POST http://127.0.0.1:19280/character-lock \
  -H "Content-Type: application/json" \
  -d '{"character":"daangni"}'

Response:

{"success": true, "character": "daangni"}

On an invalid character, the response is {"success": false, "error": "Invalid character: <character>", "validCharacters": ["auto", "vibemon", "clawd", "codex", "kiro", "claw", "daangni"]}.


Dashboard

GET /

Return the App identity, version, and documentation URL as JSON. Open the dashboard from the VibeMon tray icon; the loopback service does not serve a second HTML dashboard.

curl http://127.0.0.1:19280/

GET /dashboard-data

Get the focused project, character lock, and tracked project states as JSON for local integrations.

curl http://127.0.0.1:19280/dashboard-data

Response:

{
  "health": "ok",
  "version": "<installed-app-version>",
  "focusedProject": "my-project",
  "characterLock": "auto",
  "projects": [
    {"project": "my-project", "state": "working", "focused": true},
    {"project": "other-project", "state": "idle", "focused": false}
  ]
}

System

GET /health

Health check endpoint.

curl http://127.0.0.1:19280/health

Response:

{"status": "ok"}

GET /debug

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": [...],
  "window": {"projectId": "my-project", "bounds": {...}, "state": "working"},
  "focusedProjectId": "my-project",
  "trackedProjects": ["my-project", "other-project"],
  "alwaysOnTopMode": "all",
  "platform": "darwin"
}

POST /quit

Quit the application.

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

HTTP Status Codes

Code Description
200 Success
400 Bad request (validation error)
403 Request host or browser origin is not local
404 Not found
408 Request timeout
413 Payload too large (>10KB)
415 JSON endpoint called without application/json
429 Too many requests (rate limited)
500 Internal server error

Error Response Format

{"error": "Error message description"}

Routes not matching any known endpoint return a plain-text 404 Not Found body, not JSON.