---
title: Streams
description: Live frames and pty bytes over the websocket gateway.
---

Frames and pty bytes do not go through the API — they go through a gateway the API mints a
short-lived ticket for. Both SDKs do the ticket, the socket and the frame decoding for you.

In Python this needs the realtime extra:

```bash
pip install "raster[realtime]"
```

## Screen

<CodeGroup>

```ts TypeScript
const stream = await machine.screen.stream();

for await (const frame of stream) {
  frame.seq; // monotonic
  frame.width;
  frame.height;
  frame.image; // Uint8Array — a JPEG
}

stream.close();
```

```python Python
with machine.screen.stream() as stream:
    for frame in stream:
        frame.seq      # monotonic
        frame.width
        frame.height
        frame.image    # bytes — a JPEG
```

</CodeGroup>

A screen stream opens a session but **never takes the input lease**, so watching a desktop cannot
evict whoever is driving it.

A frame is decoded from the gateway's 20-byte binary header rather than from JSON, so its fields
are read as properties in both languages — this is the one place the Python SDK hands back a
dataclass instead of a `TypedDict`.

## Terminals

<CodeGroup>

```ts TypeScript
const term = await machine.terminal.connect(); // opens a pty if you pass no id

term.write("ls -la\n");
term.resize(120, 32);

for await (const bytes of term) process.stdout.write(bytes);

term.close(); // closes the pty inside the machine
```

```python Python
with machine.terminal.connect() as term:   # opens a pty if you pass no id
    term.write("ls -la\n")
    term.resize(120, 32)

    for chunk in term:
        print(chunk.decode(errors="replace"), end="")
```

</CodeGroup>

Bytes go both ways unmodified. Pass an existing terminal id to attach to one you already opened
with [`terminal.open`](/docs/sdk/terminal#interactive-terminals); with no id, one is opened first,
which is what makes the common case a single call.

A terminal that already has a client refuses a second at the handshake. In TypeScript, `connect`
also takes the `open` options (`cols`, `rows`, `command`, `cwd`, `user`) for the pty it creates;
in Python it takes `cols` and `rows`.

`term.exitCode` (TypeScript) and `term.exit_code` (Python) are the shell's exit code once it has
exited, and null while it is running.

## Lifetime and failure

Closing the connection closes the pty inside the machine. Closing a screen stream just stops the
frames.

A refused handshake surfaces as a `RasterError` carrying the gateway's close code translated into
the product's vocabulary — `unauthenticated` for an expired ticket, `conflict` for a terminal that
already has a client, `compute_unavailable` when the control plane could not be reached. That last
one is worth retrying; the others are not. See
[close codes](/docs/reference/realtime#close-codes).

There is no automatic reconnect. A closed socket stays closed, and a caller that wants to resume
mints a fresh ticket by calling `stream()` or `connect()` again.

## Options

TypeScript accepts an `AbortSignal` and a `webSocket` implementation for environments without a
global one:

```ts
const controller = new AbortController();
const stream = await machine.screen.stream({ signal: controller.signal });
```

For what travels on the wire — channels, the frame header, backpressure and the fence — see
[the realtime gateway](/docs/reference/realtime).
