Skip to content

Guides

Web terminal

Open an interactive login shell in a running machine from the console or any WebSocket client, and handle its messages, limits, and close reasons.

On this page

The web terminal is an interactive login shell in a running machine, carried over a WebSocket. It is full machine access, so it is allowed exactly like running a command: Owner, Admin, or Operator members, and API keys with the use_existing preset or higher. Viewer and Billing members get 403 forbidden; a read_only key gets 403 insufficient_scope.

Each connection starts a new shell. Closing the tab, reconnecting, or losing the network ends that shell and whatever it was running in the foreground; run tmux or screen in the machine for sessions that should outlive a connection, or start long jobs as background commands.

In the console

Open a machine, choose the Terminal tab, and pick Shell. Connect opens a terminal sized to the panel; the status line shows who you are signed in as on the machine. The terminal follows the light or dark theme, resizes with the window, and has Copy, Paste, and font-size controls. Disconnect ends the shell; Reconnect opens a new one. If the machine isn't running, the panel offers to start it.

The console never records what you type or what the shell prints.

Connect from a browser

Browsers can't put headers on a WebSocket, so a session first mints a single-use ticket:

curl
curl -X POST "$VMYARD_API_URL/v1/machines/$MACHINE_ID/terminal/tickets" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "X-Organization-ID: $VMYARD_ORGANIZATION_ID"
Response 201
{
  "ticket": "vyt_…",
  "expires_at": "2026-10-12T12:00:30Z",
  "url": "/v1/machines/6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f/terminal?ticket=vyt_…"
}

The ticket works once, within 30 seconds, for that machine and the session that minted it. Add the initial size to url and connect with the session cookie:

JavaScript
const socket = new WebSocket(
  `wss://api.example.com${ticket.url}&cols=${cols}&rows=${rows}`,
);
socket.binaryType = "arraybuffer";

The Origin of the page must be the console's configured address (ui_base_url), or the upgrade is refused with 403 origin_not_allowed. An expired, used, or foreign ticket is 403 terminal_ticket_invalid; mint a new one.

Connect with an API key

Scripts skip the ticket and send the key on the upgrade request. Any WebSocket client works, for example websocat:

websocat
websocat --binary \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  "wss://api.example.com/v1/machines/$MACHINE_ID/terminal?cols=120&rows=40"

An Origin header, if sent, must still be allowed. cols and rows are 1–1000 and default to 80×24; other values are 422.

Messages

DirectionKindContent
Server → clientText{"type":"opened","terminal_id","user"}, sent first; user is the login on the machine.
Server → clientBinaryTerminal output, at most 64 KiB per message.
Client → serverBinaryKeystrokes and pasted text (UTF-8), at most 64 KiB per message.
Client → serverText{"type":"resize","cols","rows"} (1–1000 each).
Client → serverText{"type":"signal","signal"}: hup, int, quit, kill, term, usr1, usr2, cont, stop, tstp, or winch, sent to the foreground process group.
Client → serverText{"type":"close"} ends the shell.
Server → clientText{"type":"exit","exit_code","signal"}: the shell ended with that status, or was killed by that signal.
Server → clientText{"type":"closed","reason"}: the terminal ended without the shell's status.

exit and closed are final: the server then closes the WebSocket with code 1000 (1008 after an invalid message, 1001 when the server is shutting down). Any other text message from the client ends the terminal as invalid_message.

Send a resize when the visible size changes, not on every pixel: the console waits for the size to settle for 150 ms. Send keystrokes as binary messages; a text message is always parsed as control.

Keepalive and idle time

The server pings every 15 seconds and ends a terminal whose client sent nothing, not even a pong, for 45 seconds (client_lost). Browsers answer pings on their own. A terminal with no input and no output for the deployment's idle timeout (30 minutes by default) ends as idle_timeout.

Output is acknowledged to the machine only as it is delivered, so a slow reader pauses the program in the machine rather than losing output.

Limits

LimitDefaultAnswer
Open terminals per user in an organization4429 terminal_limit_reached
Open terminals per organization8429 terminal_limit_reached
Opens per user per minute20429 rate_limited with Retry-After
Terminals per machine, and per host16, 64409 terminal_refused

Operators can change the first three. Close terminals you no longer use; a tab left open keeps its terminal counted until it closes.

Close reasons

reasonMeaning
exitThe shell exited.
client_closedThe client sent close or closed the WebSocket.
client_lostThe client sent nothing for 45 seconds.
idle_timeoutNo input or output for the idle timeout.
invalid_messageThe client sent a text message that isn't a control message.
server_shutdown, replica_lostThe API server restarted or stopped. Reconnect.
channel_lost, host_lost, peer_lost, shutdownThe machine's host became unreachable or restarted.
sandbox_sleeping, sandbox_destroyedThe machine stopped or was deleted.
revokedThe organization was suspended. Reconnecting is refused with 403 organization_suspended until the suspension is lifted.
replaced, fenced, identity_reset, invalid_frame, upgrade_failedThe connection to the machine was reset. Reconnect.

Every open and close is recorded in the machine's audit log with who opened it, its size, how long it lasted, and why it closed. Keystrokes and output are never stored.

Errors

StatusCodeWhat to do
403forbiddenAsk an Owner or Admin for the Operator role.
403insufficient_scopeUse a key with the use_existing preset or higher.
403session_requiredAPI keys don't mint tickets; connect with the key directly.
403origin_not_allowedOpen the console from its configured address.
403terminal_ticket_invalidMint a new ticket and connect within 30 seconds.
409machine_not_runningStart the machine and wait until it is running.
409terminal_refusedThe machine or its host has too many terminals. Close some.
429terminal_limit_reachedClose another terminal first.
429rate_limitedWait Retry-After seconds.
503terminals_unavailableThis deployment has no runtime for terminals.
503runtime_unavailableThe machine's host can't be reached. Retry shortly.

See Errors for the problem format.