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.