WebSocket protocol
Protocol version 1.0. Served live and machine-readable at GET /v1/protocol.
Transport
control_plane: JSON text WebSocket frames (InterfaceMessage).data_plane: Raw binary WebSocket frames for chunk upload/read.rest: All non-WebSocket endpoints are described by /openapi.json.
Endpoints
/v1/interfaces/ws: Create/attach any Interface; call/chunk/read/emit./v1/executions/ws: Attach to an existing Execution by uuid.
Request headers
| Header | Required | Description |
|---|---|---|
X-Machinable-Project | no | Project directory to target (also ?project=); must be under the server's project_roots allowlist. Defaults to the launch project. |
X-Machinable-User | no | Attributes created interfaces to this user (created_by). Defaults to the server's OS user. |
X-Machinable-Python | no | Interpreter to serve the request (also ?python=); a non-gateway interpreter is routed to a subprocess worker. |
Authorization | no | Bearer <token> when the server sets api_token. |
Frames
type | Direction | Purpose |
|---|---|---|
connect | client→server | Create or attach to an interface and bind the project. |
connected | server→client | Confirms the connection; returns uuid and resolved config. |
call | client→server | Invoke a public method on the connected interface. |
result | server→client | Single return value of a call. |
stream | server→client | One yielded value from a generator method; final=true closes. |
event | server→client | Unsolicited push from Interface.emit() (no call in flight). |
error | server→client | Failure detail; payload.code='not_found' for missing reads. |
chunk_start | client→server | Begin a binary upload (write/append) or read (mode read). |
chunk_end | client→server | Finish the active upload (after one or more binary frames). |
chunk_done | server→client | Upload/read complete (bytes_written / bytes_sent). |
widget_get | client→server | Request a widget's full synced state. |
widget_state | server→client | A widget's full synced state (reply to widget_get). |
widget_set | client→server | Apply synced-state changes (model.set + save_changes). |
widget_change | server→client | Trait changes (reply to widget_set, or server-originated). |
widget_msg | either | Custom widget message (model.send / msg:custom). |
ping | client→server | Liveness probe; answered with pong. |
pong | server→client | Reply to ping. |
close | either | Teardown; the server evicts the interface from its cache. |
connect fields:
target(string) (required): Module path to instantiate, or an existing uuid.version(array): Version list passed to Interface.make().uuid(string): Client-supplied id for create-by-id (content hash); idempotent in that the same id attaches to the existing instance.label(string): Optional mutable label to set on the instance.project(string): Project dir to bind (overrides header/query).meta(object): Client metadata stored with the interface.
connected fields:
payload.uuid(string): Interface uuid.payload.config(object): Resolved configuration.
call fields:
method(string) (required): Public method name (no leading underscore).args(array): Positional arguments.kwargs(object): Keyword arguments.
result fields:
payload(any): JSON-encoded result.
stream fields:
payload(any): One item; null on the terminal frame.
event fields:
payload(any): Emitted payload.
error fields:
payload.message(string): Message.payload.code(string): Typed code, e.g. 'not_found' on a missing read.
chunk_start fields:
id(string) (required): Correlation id; must match chunk_end on upload.mode(string): "write" (truncate), "append", or "read".path(string): Relative path under interface dir (upload only).params(object): Opaque, interface-defined read parameters (read only); passed verbatim to the interface read() hook.
chunk_done fields:
payload(object): bytes_written (upload) or bytes_sent (read).
widget_get fields:
id(string): Correlation id.
widget_state fields:
payload.state(object): Full anywidget model state.
widget_set fields:
changes(object): Changed keys/values to apply.
widget_change fields:
payload.changes(object): Normalized changed keys/values.
widget_msg fields:
content(any): Message body (client→server); reply in payload.
Binary flows
upload: Write opaque bytes to a path under the interface directory.
client → chunk_start {id, path, mode: write|append}
client → <binary frame> ×N
client → chunk_end {id}
server → chunk_done {path, bytes_written}read: Stream the interface read() hook's bytes back, bounded.
client → chunk_start {id, mode: read, params}
server → <binary frame> ×N (bounded)
server → chunk_done {bytes_sent}
server → error {code: not_found} # when missingInterface hooks
Implemented by project interfaces:
<public methods>
def method(self, *args, **kwargs) -> Any | IteratorAny non-underscore method is callable via call. Returning a generator streams as stream frames. Restrict with a class __api_methods__ allowlist.
read
def read(self, params: dict) -> bytes | Iterable[bytes]Binary read hook for chunk_start mode=read. params is opaque to machinable. Raise machinable.errors.NotFound (or FileNotFoundError) to emit error{code:'not_found'}.
emit
self.emit(payload: Any) -> NonePush an unsolicited event frame to a connected client during a call (e.g. progress).
widget_state / widget_update / widget_message
machinable.Widget hooks (see the Widgets guide)A machinable.Widget ships an anywidget frontend (_esm/_css) and backs the model protocol: widget_state() (default: read-only config), widget_update() (rejects by default since config is immutable; keep state via save_file or mark), and widget_message(). Pushes via self.push_widget_change/message().
local_directory
self.local_directory(*append, create=False) -> strWorking directory where uploaded chunks land and read() reads from. Machinable never parses the bytes.
Capabilities
| Capability | Summary | Surfaces |
|---|---|---|
| remote_call | Invoke any interface method over REST or WS, incl. streaming. | POST /v1/interfaces/call; ws: call/result/stream |
| binary_upload | Stream opaque bytes into an interface directory. | `ws: chunk_start(write |
| binary_read | Stream interface bytes back via its read() hook. | ws: chunk_start(read) |
| config_search | Query instances by config (operators), sort, and paginate. | POST /v1/interfaces/search |
| create_by_id | Content-addressed, idempotent create with a client id. | POST /v1/interfaces (uuid); ws: connect(uuid) |
| mutable_label | Rename via a mutable label (config stays immutable). | PATCH /v1/interfaces/{uuid}/label |
| creator_attribution | Durable created_by metadata, queryable and reindex-preserved. | X-Machinable-User header; search/find created_by |
| multi_project | Serve many projects (and interpreters) from one gateway. | X-Machinable-Project; X-Machinable-Python |
| source_edit | Read/write/create/rename interface source (token-gated). | GET/PUT/DELETE /v1/source/{path}; POST /v1/source/move |
| widgets | Interfaces ship an anywidget frontend for notebook + web. | `GET /v1/project/{module}/widget/{esm |