Server API
Everything hive does goes through the server's API: the app, the CLI and your scripts all use it. The server describes its own API, so what you read from it always matches the server you are talking to.
hive api list
hive api describe session.new_split
hive api call server.version
Each method has a name, a description, and its arguments and result as JSON Schema. A misspelled argument is an error, not ignored.
Calling it directly
The server listens on a Unix socket. hive server status prints its path.
curl --unix-socket "$HIVE_SERVER" http://hive/v1/call \
-H 'content-type: application/json' -d '{"method":"session.list"}'
The server accepts only JSON, and refuses requests from browsers (any
Origin or Sec-Fetch-* header), so a web page can't drive your terminals.
A server can also listen on your tailnet, with the same paths:
curl https://box.example.ts.net/v1/call \
-H 'content-type: application/json' -d '{"method":"session.list"}'
The API is the same on every listener; only who gets in differs. Every
request is identified before it reaches a method, and whoami says how:
| Principal | Who |
|---|---|
peer:UID |
A local account, on the Unix socket |
tailscale:alice@github |
A person, by their Tailscale login |
tailscale:tag:ci |
A tagged device, which never counts as its owner |
anonymous |
A caller on a plain TCP listener |
GET /v1/file?path=P&block_id=B returns a file the pane B printed, as the
apps' file viewer reads it. A relative path is looked for in the folder of
the pane's program, then its shell's, then where it started; ~/ and a
:line suffix work. A file comes as it is, with Range for a part of it,
and headers: X-Hive-Path (the path resolved), X-Hive-Kind (file or
dir), X-Hive-Text: 1 when it reads as text, X-Hive-Line when the path
named a line. A folder comes as JSON, {path, entries: [{name, dir, size, modified}], truncated}, newest first, at most 500. HEAD says the same
without the body. Only files and folders: never pipes or devices. Anyone a
server lets in can already read files through a terminal; a read from
another machine is logged with who asked.
A call answers with {"result": …}, or with {"error": {"status", "code", "message"}} and an HTTP status:
| Code | Status | Means |
|---|---|---|
bad_request |
400 | The arguments were wrong |
not_found |
404 | No such session, window or block |
method_not_found |
404 | No such method |
ambiguous |
409 | A target matched more than one thing |
conflict |
409 | Another client holds the size, or a label is taken |
forbidden |
403 | This caller may not use this server |
permission_denied |
403 | A script called a method its permissions don't cover |
timeout |
408 | A script's wait or time limit ran out |
script_unloaded |
409 | The script stopped while the call was running |
Methods
Method names are stable. They break only in a new major version, and the changelog lists every change.
| Group | Methods |
|---|---|
| Sessions | session.list, session.create, session.view, session.list_blocks, session.set_label, session.close |
| Windows | session.new_window, session.close_window, session.focus_window, session.focus_next_window, session.focus_previous_window, session.move_window, session.set_window_label, session.pin_window, session.balance_window |
| Blocks | session.new_split, session.move_block, session.describe_block, session.focus_block, session.focus_direction, session.focus_next_pane, session.focus_previous_pane, session.zoom_block, session.resize_pane, session.set_block_label, block.close, block.method |
Terminals, as hive.terminal.NAME |
format, search, write, send_key, resize, size, title, pwd, process, program_status, list_dir, wait |
| Agents | agent.report, agent.list, agent.act |
| Notifications and badges | notify.send, notification.list, notification.ack, status.set, status.clear, status.list |
| Actions and keys | action.list, action.run, keymap.get, client.perform |
| Settings | config.get, config.set, config.reload (the server's own settings file; see Settings) |
| Server | server.version, server.flush, server.shutdown, api.list, api.describe, target.resolve, client.list, whoami |
| Scripts | script.run, script.stop, script.list, script.result, script.grant, script.revoke, script.enable, script.disable, script.set_mode, script.set_option, script.reload, script.logs, audit.list (see Scripts) |
Each method also says which permission a script
needs to call it: hive api list --json and hive api describe show it as
permission.
Terminal methods take the block and their own arguments separately:
hive api call hive.terminal.format '{"block_id":"block:…","args":{"trim":true}}'
hive api call hive.terminal.format '{"block_id":"block:…","args":{"tail":2}}'
format takes format (text or vt), trim, unwrap, and tail: the
screen's last N lines that show something. hive api describe METHOD shows
every method's arguments.
Events
GET /v1/events streams events, one JSON object per line.
| Query | Streams |
|---|---|
| (none) | Sessions created and destroyed |
?session=ID |
Everything in one session, terminal events included |
?all=1 |
Everything, clients included |
?types=a,b |
Only events of these types, such as script_log,script_ended |
?since=N |
First, the events after number N that are still kept |
Every event has a number. A client that reconnects passes the last number it
saw and misses nothing, unless the gap is older than the server keeps. Then it
gets resync_required and should read the state again. A heartbeat comes
every 15 seconds, so a client can tell a quiet server from a lost connection.
Scripts' events, script_started, script_log, script_ended,
script_loaded, script_unloaded, script_failed and
script_quarantined, come only to a stream that names them with
?types=, so a busy script never crowds the apps' stream.
With ?all=1, the server also sends server_started, restore_finished,
config_changed, keymap_changed, notification, notification_acked
and action_run.
A pane's badges change with the terminal event status_changed.
With ?all=1, server events come too: client_joined and client_left
when an app or a phone connects or goes, and tailscale_status_changed for
built-in Tailscale (its state, and the sign-in link when it needs one).
A terminal event arrives inside a session event:
{"seq":42,"type":"session_event","payload":{"session_id":"session:…",
"event":{"type":"block_event","payload":{"block_id":"block:…",
"creator_name":"hive.terminal","name":"child_exited","payload":{"exit_code":2}}}}}
Session events include window_created, window_closed, window_pinned,
window_label_changed, session_label_changed, block_created,
block_closed, session_view_changed and active_window_changed. Terminal
events include child_exited, title_changed, pwd_changed, bell,
process_changed, size_changed, desktop_notification,
progress_report, program_status_changed and agent_changed.
progress_report is a program's progress (OSC 9;4): state (set,
error, indeterminate, pause or remove) and progress, a
percentage. The block's view keeps the last one as progress
({"state", "progress"}) until it's removed, the program exits or the
shell is back in front, so a client that connects later shows it too.
agent_changed carries harness, session_id (the agent's own), state,
message, kind (for a blocked agent: permission_prompt or
elicitation_dialog, a question), revision (it moves on whenever state,
message or kind change), updated_at, cwd, and event: what changed
it. That is a hook's event name (SessionStart,
Stop…), or one of the server's own:
event |
Why |
|---|---|
program_status |
The agent reported its own state (OSC 7501), such as idle after an interrupt, which runs no hook |
shell_returned |
The pane's interactive shell came back in front: the agent exited |
agent_returned |
The same agent is in front again after shell_returned |
agent.report takes an optional agent_pid, the process the hook runs
for. A report from a different agent than the one holding the pane (one
another agent runs as a tool) is ignored. hive agent hook sends it.