> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSockets

> Use WebSocket channels for streaming exec, PTY, watches, lifecycle, monitor, and exposed ports.

## Exposed-port WebSockets

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  info = machine.get_host_info(8080)
  print(info.websocket_url)
  ```

  ```typescript TypeScript SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  const info = await machine.getHost(8080);
  console.log(info.websocketUrl);
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine url mch_123 8080 --websocket
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/host" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{ "port": 8080 }'
  # read websocket_url from the response
  ```
</CodeGroup>

Use this for WebSocket servers that your machine exposes through the preview
URL router. The returned URL is signed and temporary. Direct HTTP preview
continuation cookies do not authorize WebSocket upgrades; use the signed
`websocket_url`.

Default SSH access does not use an exposed-port WebSocket. Use
[`nullspace ssh`](../access/ssh) for certificate-backed relay access. Only older
port-22 fallback deployments use a signed `websocket_url` with OpenSSH and
`websocat`.

For the self-hosted single-host appliance, owned-domain mode returns signed
`wss://{PORT}-{MACHINE_ID}.<domain>/...` preview WebSocket URLs through
API-compatible Caddy ingress. Localhost/no-domain mode leaves
`NULLSPACE_PUBLIC_HOSTNAME` unset, so exposed-port helpers use direct local
host mappings instead of signed public WebSocket URLs.

## Machine control WebSocket

The machine control channel powers streaming exec, PTY sessions, and file watch
events. Prefer SDK helpers unless you are implementing a custom client.
The server sends WebSocket Ping frames every 15 seconds and closes after two
missed Pong responses, so custom clients need normal Ping/Pong handling.

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine.commands.run(
    "for i in 1 2 3; do echo $i; sleep 1; done",
    shell=True,
    on_stdout=lambda data: print(data, end=""),
)
```

## Lifecycle and monitor streams

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Lifecycle, Monitor

lifecycle = Lifecycle()
sub = lifecycle.subscribe(on_event=lambda event: print(event.operation, event.status))
sub.stop()

monitor = Monitor()
monitor_sub = monitor.subscribe(
    on_metrics=lambda event: print(event.metrics),
    on_processes=lambda event: print(event.processes),
)
monitor_sub.stop()
```

## Protocol reference

For raw message shapes, see [WebSocket protocol](../reference/websocket-protocol).

## Related

* [PTY sessions](../access/pty)
* [SSH access](../access/ssh)
* [Access control](./access-control)
* [Token model](./token-model)
* [Lifecycle events](../observability/lifecycle-events)
* [Monitor](../observability/monitor)
* [Self-hosted single-host](../quickstarts/self-hosted-single-host)
