Connection wire protocol
Connect to machines from any language over WebSockets and HTTPS.
If you use our SDKs or CLI, you don't need to read this. The SDKs handle the wire protocol for you. If you want to manually access a machine's file system, connect to its shell, or run a command, this page explains the message formats and connection details.
What you'll learn
- How to get an access token and connection URLs
- The message formats for commands, interactive shells, and files
- Close codes and error handling
Before you start
- An API key
- Connect with the SDK, which shows what each protocol does
Get an access token
Request a token for the kind of access you need: shell, files, or ports. Tokens last 3600 seconds by default and up to 86400.
A shell token works for both the shell and exec URLs. For ports, pass "ports": [3000] or "ports": "*" in the body, and urls.ports maps each port to its URL.
Authenticate a WebSocket
Send the token as a WebSocket subprotocol, next to the protocol name. Headers and query parameters aren't accepted. Browsers can connect this way, since they can't set WebSocket headers.
Call a port
Ports are plain HTTPS. Send the token in the Metorial-Sandbox-Token header. Public ports need no token.
Multiplexed framing
The exec and files protocols run many requests over one socket. Every request has a client-chosen id, and every reply carries it.
- Text frames are JSON objects with an
id. - Binary frames start with one byte holding the length of the
id, then theidin UTF-8, then the payload.
Exec
Use the urls.exec socket to run commands and stream their output. The SDKs' command() and spawn() use it.
Run a command
command is an argv array. cwd, env, and timeoutMs are optional. Set "stdin": true to keep standard input open for writing.
Messages from the client
| Message | Purpose |
|---|---|
Binary frame [id][bytes] | Writes to the command's stdin |
{ "id", "type": "stdin_eof" } | Closes stdin |
{ "id", "type": "signal", "signal": "TERM" } | Sends a signal. Use names without SIG. |
Messages from the server
| Message | Meaning |
|---|---|
{ "id", "type": "started", "pid" } | The process started |
Binary frame [id][stream][bytes] | Output. The stream byte is 1 for stdout and 2 for stderr. |
{ "id", "type": "exit", "code", "signal", "timedOut" } | The process ended |
{ "id", "type": "error", "error": { "code", "message", "retryable" } } | The request failed |
Interactive shell
Use the urls.shell socket for a terminal session. The CLI's shell command uses it. Each socket carries one session.
Open a session
Send open as the first message:
command (an argv array), cwd, and env are optional. Leave out command for an interactive shell. cols and rows go from 1 to 1000 and only apply when tty is true.
The server answers with { "type": "session_init", "sessionId": "shs_…", "offset": 0 }.
Streams
- Client binary frames are stdin.
- With
tty: true, server binary frames are raw terminal output. - Without a TTY, the first byte of each server binary frame is
0x01for stdout or0x02for stderr.
Control messages from the client are { "type": "resize", "cols", "rows" }, { "type": "signal", "signal": "INT" }, and { "type": "stdin_eof" }. When the process ends, the server sends { "type": "exit", "code", "signal" }.
Resume after a dropped connection
Track how many output bytes you've received. If the socket drops without a normal close, open a new socket and attach to the session instead of opening a new one:
The server replays output from offset. If some of it is no longer buffered, it first sends { "type": "output_gap", "from", "to" }. Closing the socket with code 1000 ends the session for good.
Files
Use the urls.files socket to read and write files. Requests use the multiplexed framing, and file content travels in binary frames of up to 64 KiB. All paths are absolute.
| Request | Replies |
|---|---|
{ "id", "op": "stat", "path", "followSymlinks" } | result with entry |
{ "id", "op": "list", "path", "recursive", "maxDepth", "limit" } | One or more entries messages, then result with count and truncated |
{ "id", "op": "read", "path", "offset", "length" } | start with size, mtime, and offset, then binary chunks, then result with bytes |
{ "id", "op": "write", "path", "overwrite", "parents", "mode": "0644" } | result with bytes and sha256 |
{ "id", "op": "mkdir", "path", "parents", "mode" } | result with created |
{ "id", "op": "move", "from", "to", "overwrite" } | result |
{ "id", "op": "delete", "path", "recursive", "missingOk" } | result with deleted |
Write a file
After the write request, send the content as binary frames, then finish with the SHA-256 of the whole content. The server rejects the write with checksum_mismatch if the hashes differ.
write also accepts append, sync, and the expected total size.
File entries
stat and list return entries with type (file, directory, symlink, or other), size, mode, mtime in milliseconds, and target for symlinks. Non-recursive listings set name. Recursive listings set path, relative to the listed directory.
Cancel a request
Send { "id": "c1", "op": "cancel", "target": "r1" } to cancel request r1, or { "id": "w1", "type": "abort" } to abort an upload in progress.
Errors
| Code | Meaning |
|---|---|
not_found | The path doesn't exist |
already_exists | The destination exists and overwrite is off |
not_a_directory, not_a_file, is_a_directory | The path has the wrong type for the operation |
not_empty | The directory isn't empty and recursive is off |
checksum_mismatch | The uploaded content doesn't match its sha256 |
unknown_op | The server doesn't support the operation |
io_error | The file system returned an error |
connection_interrupted | The connection dropped. Retry the request. |
Connection notices
Idle sockets close after the machine's connection idle timeout. Before that, the server sends notices:
notice is idle_warning, idle, or closing, and closing can include a reason.
Close codes
| Code | Meaning | What to do |
|---|---|---|
1000 | Normal close. Reason idle_timeout means the socket was idle. | Reconnect when needed |
1012 | The machine paused, is restarting, or terminated. The reason says which. | Start the machine, then reconnect |
4401 | The token is invalid or expired | Request a new token and reconnect |
4404 | The machine or session wasn't found | Check the ID |
4410 | The machine was terminated, or the shell session ended | Don't retry |
4429 | Too many requests | Back off and retry |
4503 | The machine is unavailable. Reason machine_failed means it failed. | Retry later, or check the machine's failure |
The shell socket adds these:
| Code | Meaning |
|---|---|
4000 | The session was detached |
4400 | The open or attach message was invalid |
4409 | Another socket is attached to the session |
4500 | The session failed to start |
4503 | Too many sessions are open on the machine |