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.
| 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 |
| 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) |
characteris a visual rendering choice, typically selected by the agent bridge. It is not a general agent identity field.
| 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 |
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
statevalue is rejected with a400error.
Agent bridges usually set character automatically:
clawd for Claude Codecodex for Codex CLIkiro for Kiroclaw for OpenClawopencode for OpenCode (currently rendered as vibemon because opencode is absent from the canonical character registry)vibemonUnknown 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: trueis added when the update didn’t change the visible window — either because neitherstatenor 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 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"}
}
}
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"}
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 current character lock.
curl http://127.0.0.1:19280/character-lock
Response:
{"character": "auto"}
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"]}.
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 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}
]
}
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": [...],
"window": {"projectId": "my-project", "bounds": {...}, "state": "working"},
"focusedProjectId": "my-project",
"trackedProjects": ["my-project", "other-project"],
"alwaysOnTopMode": "all",
"platform": "darwin"
}
Quit the application.
curl -X POST http://127.0.0.1:19280/quit
| 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": "Error message description"}
Routes not matching any known endpoint return a plain-text
404 Not Foundbody, not JSON.