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

# SDK

> Create and manage persistent VMs with the Python or JavaScript SDK

`VM` creates and manages persistent CPU Linux machines with a durable root,
systemd, optional Docker and SSH, a browser terminal, and an optional desktop.
Choose Python or JavaScript below. Selecting a language switches every section
on this page. JavaScript examples also work in TypeScript.

## Installation and configuration

<Tabs>
  <Tab title="Python">
    Install `beam-client` and provide a Beam token, or sign in with `beam login`.

    ```bash theme={null}
    python -m pip install --upgrade beam-client
    export BEAM_TOKEN=YOUR_BEAM_TOKEN
    ```

    Save this example as `vm.py` and run it with `python vm.py`:

    ```python theme={null}
    from beam import VM

    vm = VM(
        "agent-workspace",  # Optional; otherwise generated.
        cpu=1,
        memory=2048,  # MiB.
        desktop=True,
        protected_ports=[7681, 8080],
        ttl=300,  # Idle seconds; 0 keeps running.
        idle_action="pause",
        auto_resume=True,
    ).create()
    try:
        command = vm.process.exec("node", "--version")
        command.wait(timeout=30)
        print(command.stdout.read())
        vm.fs.write_text("/workspace/hello.txt", "hello")
        png = vm.desktop.screenshot("./desktop.png")
        print(vm.id)
        vm.pause()
    finally:
        vm.close()  # Release client connections.
    ```

    The example leaves the VM paused. Save `vm.id` and use `VM.connect(id)` for the
    next session; `vm.remove()` deletes it. Closing the client or exiting a `with`
    block does not stop or delete the VM.

    The default image is Ubuntu 22.04, with 4 CPU cores, 8192 MiB of RAM and a
    50 GiB writable root. Python uses CPU cores and memory in MiB. Use
    `context="PROFILE"` to select a named CLI profile, or pass a `ConfigContext`
    for explicit credentials and gateway settings. VMs are CPU-only.
  </Tab>

  <Tab title="JavaScript">
    Install the SDK and configure a server-side Beam token:

    ```bash theme={null}
    npm install @beamcloud/beam-js@1.1.4
    export BEAM_TOKEN=YOUR_BEAM_TOKEN
    ```

    Save the following example as `vm.mjs` and run it with `node vm.mjs`. The
    JavaScript examples also work in TypeScript; the package includes declarations.

    ```javascript theme={null}
    import { beamOpts, VM } from "@beamcloud/beam-js";

    beamOpts.token = process.env.BEAM_TOKEN || "";
    beamOpts.workspaceId = process.env.BEAM_WORKSPACE_ID || "";

    const vm = await VM.create({
      name: "agent-workspace", // optional; otherwise generated
      cpu: 1, // cores, or "1000m"
      memory: "2Gi", // MiB or a quantity
      desktop: true,
      protectedPorts: [7681, 8080],
      idleTimeout: 300, // idle seconds; 0 keeps running
      idleAction: "pause", // "stop" or "pause"
      autoResume: true,
    });

    const command = await vm.exec(["node", "--version"]);
    await command.wait({ timeoutMs: 30_000 });
    console.log(await command.stdout.read());

    await vm.fs.writeText("/workspace/hello.txt", "hello");
    const png = await vm.desktop.screenshot("./desktop.png");
    await vm.pause();
    await vm.close(); // close local tunnels/watchers
    ```

    The example leaves the VM paused. Save `vm.id` and use `VM.connect(id)` for the
    next session; `vm.remove()` deletes it. For a plain JavaScript workflow that
    also checkpoints, forks, reconnects and cleans up, see
    [the runnable agent workspace example](https://github.com/beam-cloud/beam-client/blob/master/js/examples/vm-agent-workspace.mjs).

    `new VM(config).create()` is equivalent. The default image is Ubuntu 22.04,
    with 4 CPU cores, 8192 MiB of RAM and a 50 GiB writable root. VMs are CPU-only;
    GPU and unknown configuration fields are rejected. If `workspaceId` is omitted,
    the SDK resolves the token's current workspace. Set it explicitly when selecting
    a workspace. Authentication and gateway settings use the existing `beamOpts`.
  </Tab>
</Tabs>

## Find, connect and manage lifecycle

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from beam import VM

    with VM.get("agent-workspace") as existing:  # Read-only; does not start it.
        vm_id = existing.id

    with VM.connect(vm_id) as ready:  # Starts/resumes and waits for commands.
        ready.refresh()
        print(ready.id, ready.info["status"], ready.info["spec"])
        ready.wait(timeout=180, services=True)
        ready.update(ttl=60, idle_action="pause", auto_resume=True)
        ready.update(metadata={})  # Replaces the whole map.
        ready.stop()  # Keep the root; cold boot next time.
        ready.start()
        ready.pause()  # Keep RAM and running processes.
        ready.resume()
        ready.start(cold=True)  # Discard saved RAM.
        ready.remove()  # Delete the VM and its managed root.

    fleet = VM.list(metadata={"user_id": "123"}, status="running")
    ```

    `VM.list()` returns management records. `create()`, `start()`, `connect()` and
    `fork()` wait for exec readiness by default; use `create(wait=False)` or
    `start(wait=False)` to submit, then call `wait()`. Python wait deadlines are in
    seconds. `get()` and `list()` do not wake sleeping VMs. Guest operations can
    wake a VM when `auto_resume` is enabled.

    `ttl` measures idle seconds. It stops or pauses the VM instead of deleting it.
    `stop(no_snapshot=True)` skips the visible stop snapshot while retaining the
    durable root. `pause()` saves RAM and the paired disk state.

    For an agent returning to a known workspace, use `VM.connect(name_or_id)`.
    Creation rejects a name already in use. Reuse the same `request_id` UUID and
    identical configuration when retrying a creation across application restarts.
    Repeated `create()` calls on the same prepared object retain the image, keys
    and request. `prepare()` resolves them without starting compute. A readiness
    timeout leaves the resource available for inspection and recovery.
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const existing = await VM.get("agent-workspace"); // read-only: does not start it
    const ready = await VM.connect(existing.id); // starts/resumes and waits
    const fleet = await VM.list({
      metadata: { user_id: "123" },
      status: "running",
    });

    await ready.refresh();
    console.log(ready.id, ready.name, ready.status, ready.spec, ready.metadata);
    console.log(ready.info); // full typed management response
    await ready.wait({ services: true }); // also wait for SSH/UI ports
    await ready.update({ idleTimeout: 60, idleAction: "pause", autoResume: true });
    await ready.update({ metadata: {} }); // replaces the whole map
    await ready.stop(); // keep root; cold boot next time
    await ready.start();
    await ready.pause(); // keep RAM and running processes
    await ready.resume();
    await ready.start({ cold: true }); // explicitly discard saved RAM
    await ready.remove(); // delete VM and its managed root
    ```

    `create`, `start`, `resume`, `connect` and `fork` wait for exec readiness by
    default. Pass `{ wait: false }` to return after submission, then call `wait()`.
    Startup/wait deadlines use `timeoutMs`; `signal` accepts an `AbortSignal`.
    Management results are cached on the object; call `refresh()` to read current
    state. `get()` and `list()` do not wake sleeping VMs. Guest operations wake them
    only when `autoResume` is enabled.

    For an agent returning to a known workspace, call `VM.connect(nameOrId)`.
    `VM.create({ name })` creates a new VM and reports a conflict if the name is
    already in use. Use an application-owned `requestId` when retrying the same
    creation, rather than generating another request after a timeout.

    `idleTimeout` (also accepted as Python/CLI's `ttl`) measures idle seconds. It
    stops or pauses the VM instead of deleting it. `stop({ noSnapshot: true })`
    skips creating the visible stop snapshot while retaining the durable root.
    `pause()` saves RAM plus the paired disk state. `close()` only releases local
    connections and watchers; it does not stop, pause or delete the VM.

    Creation uses a generated `requestId` UUID and freezes the prepared image,
    keys and request on the object. Repeating `create()` on that object submits
    the identical request. Supply your own UUID and identical configuration to
    recover a creation across application restarts. `prepare()` resolves the
    image, mounts and keys without starting compute. A readiness timeout does not
    delete the resource; use its name or request ID to recover and inspect it.
  </Tab>
</Tabs>

## Snapshots, forks and templates

<Tabs>
  <Tab title="Python">
    Snapshots and templates are immutable **disk** artifacts. They outlive their
    source VM and capture the root, not RAM or external shared volumes. Preserve
    running processes with `pause()` and `resume()`.

    ```python theme={null}
    from beam import VM

    with VM.connect("agent-workspace") as vm:
        snapshot = vm.snapshot("before-edit")
        print(snapshot["id"], snapshot["name"])
        print(vm.snapshots())  # This VM's snapshots.

        branch = VM(
            "experiment", snapshot=snapshot["id"], cpu=1, memory=512, desktop=False,
        ).create()
        branch.fs.write_text("/workspace/experiment.txt", "independent root")
        branch.remove()
        branch.close()

        current = vm.fork("current-root-copy")
        current.remove()
        current.close()

        template = vm.create_template("dev-base", description="Tools installed")
        next_vm = VM("next-agent", template=template["id"], desktop=False).create()
        next_vm.remove()
        next_vm.close()
        vm.remove_snapshot(snapshot["id"])
    ```

    Python snapshot and template creation returns artifact records. Launch from
    an artifact with `VM(snapshot=ID)` or `VM(template=NAME_OR_ID)`. Omitted settings
    inherit the seed; explicit settings override it. Templates are private. Restore
    creates a new VM; there is no in-place rollback.

    Use the CLI for workspace-wide artifact listing and template management:

    ```bash theme={null}
    beam vm snapshot list
    beam vm template list
    beam vm template show dev-base
    beam vm template rm dev-base
    ```

    See [storage and snapshots](/v2/vm/storage) for external mount boundaries and
    artifact retention after deleting a source VM.
  </Tab>

  <Tab title="JavaScript">
    Snapshots and templates are immutable **disk** artifacts. They can outlive their
    source VM. A named snapshot captures the root, not RAM or external shared
    volumes. To preserve running processes, use `pause()`/`resume()`.

    ```javascript theme={null}
    const snapshot = await vm.snapshots.create("before-edit");
    console.log(snapshot.id, snapshot.name, snapshot.info);
    const snapshots = await vm.snapshots.list(); // only this VM's snapshots
    const all = await VM.snapshots.list(); // workspace, including old sources
    const saved = await VM.snapshots.get("before-edit");

    const branch = await saved.fork({ name: "experiment", cpu: 2 });
    // Equivalently: VM.create({ name: "experiment", snapshot: saved.id })
    const current = await vm.fork("current-root-copy");

    const template = await vm.templates.create({
      name: "dev-base",
      description: "Tools installed",
    });
    const templates = await VM.templates.list();
    const seed = await VM.templates.get("dev-base");
    const next = await seed.fork({ name: "next-agent", desktop: false });
    // Equivalently: VM.create({ name: "next-agent", template: seed.id })

    await vm.snapshots.delete(snapshot.id);
    await VM.templates.delete(template.id);
    // Artifact handles also support snapshot.delete() / template.delete().
    ```

    Workspace managers also support `create({ vm: nameOrId, name })` and
    `fork(nameOrId, config, options)`. `vm.snapshot(name)` and
    `vm.createTemplate(name, description)` are convenience methods. Scoped managers
    only resolve artifacts belonging to that VM. Templates are private. The backend
    restores a named snapshot into a new VM using `snapshot` or `fork`; it does not
    provide an in-place rollback operation. Omitted creation settings inherit the
    seed's settings; explicit `false`, `0` and empty arrays override them.
  </Tab>
</Tabs>

## Processes, stdin and logs

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from beam import VM

    with VM.connect("agent-workspace") as vm:
        with vm.keep_alive():
            command = vm.process.exec(
                "cat", stdin="literal text 😀\n", cwd="/workspace",
            )
            print(command.wait(timeout=10))
            print(command.stdout.read(), command.stderr.read())

            job = vm.process.exec("sh", "-lc", "npm run dev", env={"NODE_ENV": "development"})
            print(vm.process.list_processes())
            for line in job.logs:
                print(line, end="")
            job.kill()

        result = vm.process.run_code("print(6 * 7)")
        print(result.stdout)
        print(vm.metrics())
    ```

    Process handles expose PID, `wait`, `kill`, stdout/stderr streams and iterable
    `logs`. Arguments are literal; use `sh -lc` when you need shell syntax. Stdin
    accepts text, bytes or a binary file. A wait timeout ends the wait without
    killing the command. Handles belong to their original runtime; resolve new
    handles after a restart. The async process, filesystem and Docker interfaces
    are available through `vm.aio`.

    Hold `vm.keep_alive()` around long work on an idle-limited VM. Detached jobs
    and background agents need an application-owned lease or idle stopping disabled.

    Run the installed coding-agent CLIs and inspect systemd logs through the
    process API:

    ```python theme={null}
    with VM.connect("agent-workspace") as vm:
        with vm.keep_alive():
            agent = vm.process.exec("claude", "-p", "add tests", cwd="/workspace")
            for line in agent.logs:
                print(line, end="")
            print(agent.wait())

        journal = vm.process.exec("journalctl", "--no-pager", "-u", "beam-terminal.service", "-n", "200")
        journal.wait()
        print(journal.stdout.read())
    ```

    Use `beam vm prompt` and `beam vm logs` for managed background sessions and
    session/unit/PID log following. Supply agent credentials using VM environment
    variables or workspace secrets. For headless Codex, provide `CODEX_API_KEY`,
    or authenticate its guest CLI with an injected `OPENAI_API_KEY` before execution:

    ```python theme={null}
    with VM.connect("agent-workspace") as vm:
        login = vm.process.exec("sh", "-lc", "printenv OPENAI_API_KEY | codex login --with-api-key")
        if login.wait() != 0:
            raise RuntimeError(login.stderr.read())
        vm.process.exec("git", "init", "/workspace").wait()
    ```

    Codex requires its working directory to be a Git repository. See
    [Codex authentication](https://learn.chatgpt.com/docs/auth) and
    [non-interactive mode](https://learn.chatgpt.com/docs/non-interactive-mode).
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const controller = new AbortController();
    const command = await vm.exec(["cat"], {
      stdin: "literal text 😀\n",
      cwd: "/workspace",
    });
    await command.wait({ timeoutMs: 10_000, signal: controller.signal });
    console.log(await command.stdout.read(), await command.stderr.read());

    const server = await vm.execShell("npm run dev", {
      env: { NODE_ENV: "development" },
    });
    for await (const line of server.logs) console.log(line);
    const processes = await vm.listProcesses();
    const reattached = await vm.getProcess(server.pid, {
      containerId: server.containerId,
    });
    await reattached.kill();

    await vm.keepAlive(async () => {
      const job = await vm.exec(["./long-job"]);
      await job.wait();
    });
    ```

    Array arguments are passed literally. A string passed to `exec()` runs with
    `sh -lc`; `execShell()` makes shell intent explicit. `stdin` accepts text or
    bytes uploaded before execution, followed by EOF. Process handles expose
    `pid`, `containerId`, `wait`, `status`, `kill`, cached `stdout`/`stderr` streams,
    and async `logs`. Reads consume available output; iterate to wait for EOF.
    Wait timeouts and aborts end the wait without killing the process.

    Handles stay tied to their original boot. Old handles reject operations after
    the VM restarts, protecting against PID reuse. Save `{ pid, containerId }` when
    reattaching to managed processes. Following logs and waiting for a process hold
    an activity lease for their duration. Detached work needs an application-owned
    `keepAlive()` scope or idle stopping disabled. The Python-style
    `vm.process.exec`, `.execShell`, `.list` and `.get` are also available.

    `runCode(code, blocking = true, cwd?, env?)` follows Sandbox's Python execution
    convenience method. `metrics()` returns guest CPU, RAM and root disk usage.

    ```javascript theme={null}
    const journal = await vm.logs({ unit: "beam-terminal.service", lines: 200 });
    console.log(await journal.read());
    const live = await vm.logs({ session: "prompt-abc123", follow: true });
    for await (const line of live) console.log(line);
    await live.close(); // stop the log-follow process

    const foreground = await vm.prompt("add tests", {
      agent: "codex",
      cwd: "/workspace",
    });
    const session = await vm.prompt("add tests", {
      agent: "claude",
      background: true,
    });
    ```

    `logs` accepts one of `pid`, `unit` or `session`. Agent prompts use the installed
    Claude/Codex CLIs and durable session logs; configure the agent's credentials
    through VM environment variables or workspace secrets. Background prompts run
    as systemd units and return `{ session, unit }`.

    For headless Codex, provide `CODEX_API_KEY`, or sign in the guest CLI using an
    `OPENAI_API_KEY` secret before calling `prompt`:

    ```javascript theme={null}
    const login = await vm.execShell(
      "printenv OPENAI_API_KEY | codex login --with-api-key",
    );
    if ((await login.wait()) !== 0) throw new Error(await login.stderr.read());
    ```

    Codex also requires its working directory to be a Git repository. Initialize an
    empty workspace with `await (await vm.exec(["git", "init", "/workspace"])).wait()`.
    See [Codex authentication](https://learn.chatgpt.com/docs/auth) and
    [non-interactive mode](https://learn.chatgpt.com/docs/non-interactive-mode).
  </Tab>
</Tabs>

## Files, images, disks and Docker

<Tabs>
  <Tab title="Python">
    `vm.fs` provides uploads/downloads, text and byte reads/writes, directories,
    metadata, search and replacement. Use `vm.aio.fs` for asynchronous operations.

    ```python theme={null}
    from beam import DurableDisk, Image, VM, Volume

    image = Image.from_dockerfile("./Dockerfile", context_dir=".")
    with VM(
        "custom-workspace",
        image=image,
        cpu=1,
        memory=2048,
        env={"APP_MODE": "dev"},
        secrets=["API_KEY"],
        volumes=[Volume(name="shared", mount_path="/data")],
        disks=[DurableDisk(name="cache", size="10Gi", mount_path="/cache")],
        docker_enabled=True,
    ) as custom:
        custom.create()
        custom.fs.write_text("/workspace/hello.txt", "hello")
        print(custom.fs.read_text("/workspace/hello.txt"))
        run = custom.docker.run("hello-world", remove=True)
        run.wait()
        print(run.stdout, run.stderr)
        custom.stop(no_snapshot=True)
    ```

    Creation prepares registry images and Dockerfiles with the VM services.
    Images require Debian/Ubuntu with apt; desktop images require Ubuntu 22.04.
    Prebuilt image IDs must already contain those services. Use `prepare()` to
    build and resolve a launch request without starting compute, or
    `beam vm image build` for a standalone VM image build.

    Additional named disks and workspace volumes persist independently of the VM.
    Snapshot/template launches inherit the image and root; omit `image` there.
    Docker calls return result handles; call `wait()` and read their `stdout` and
    `stderr` properties. Compose helpers and async Docker calls are documented in
    [Docker and Compose](/v2/vm/docker).
  </Tab>

  <Tab title="JavaScript">
    The `vm.fs` surface matches Sandbox: `uploadFile`, `downloadFile`, `writeText`,
    `writeBytes`, `readText`, `readBytes`, `statFile`, `listFiles`, `createDirectory`,
    `deleteDirectory`, `deleteFile`, `remove`, `findInFiles` and `replaceInFiles`.
    Each operation resolves the current boot automatically.

    ```javascript theme={null}
    import { Image, Volume } from "@beamcloud/beam-js";

    const image = await Image.fromDockerfile("./Dockerfile", ".");
    const custom = await VM.create({
      image,
      env: { APP_MODE: "dev" },
      secrets: ["API_KEY"],
      volumes: [new Volume("shared", "/data")],
      disks: [{ name: "cache", size: "10Gi", mountPath: "/cache" }],
      dockerEnabled: true,
    });
    const prepared = await VM.prepareImage(image, { desktop: true });
    const built = await prepared.build(); // standalone VM image build
    if (!built.success || !built.imageId) throw new Error("VM image build failed");
    const fromId = await VM.create({ image: Image.fromId(built.imageId) });

    await custom.docker.version();
    await custom.docker.run("hello-world", ["--rm"]);
    await custom.docker.compose(["up", "-d"]);
    await custom.docker.exec(["ps"]);
    ```

    Registry images and Dockerfiles receive the same VM services as Python. They
    require Debian/Ubuntu with apt; desktop images currently require Ubuntu 22.04.
    Prebuilt IDs must already contain those services. `Image` supports build steps,
    build secrets, registry credentials and Dockerfile contexts as usual. Additional
    disks use ext4/qcow; workspace volumes persist independently of the VM. Source
    snapshot/template creation inherits its image and root; omit `image` there.
  </Tab>
</Tabs>

## URLs, protected ports and private access

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from urllib.request import Request, urlopen
    from beam import VM

    with VM.connect("agent-workspace") as vm:
        url = vm.expose(3000, protected=True)
        stable = vm.get_url(3000)
        token = vm.traffic_access_token
        with urlopen(Request(url, headers={"X-Beam-VM-Token": token})) as response:
            print(response.status)
        browser_entry = vm.access_url(3000, ttl=600)
        vm.rotate_access_token()  # Revoke the token and browser sessions.
        vm.unexpose(3000)
        vm.update_network_permissions(block_network=False, allow_list=["10.0.0.0/8"])
        vm.bind(5432)  # Make a port available to authenticated private tunnels.
    ```

    Run an application on its guest port before requesting its URL. `ports` and
    `protected_ports` configure published URLs; `expose()` publishes another port.
    `terminal_url` and `desktop_url` are stable service URLs. Browser entry URLs
    exchange an expiring credential for an HTTP-only cookie. Rotating the traffic
    token revokes that token and the browser sessions. Network rules survive restart.

    For private TCP forwarding, SSH, SCP and local directory sync, use the CLI:

    ```bash theme={null}
    beam vm port-forward agent-workspace 5432 --local-port 15432
    beam vm ssh agent-workspace -- uname -a
    beam vm scp ./file.txt agent-workspace:/workspace/file.txt
    beam vm sync agent-workspace ./project --watch
    ```

    See [networking](/v2/vm/networking) and [commands and SSH](/v2/vm/processes)
    for access policies, SSH identity, recursive transfers and sync options.
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const url = await vm.exposePort(3000, { protected: true });
    const stable = await vm.getUrl(3000);
    const token = await vm.getAccessToken();
    const response = await fetch(url, { headers: { "X-Beam-VM-Token": token } });
    const browserEntry = await vm.accessUrl(3000, { ttl: 600 });
    await vm.rotateAccessToken(); // revoke token and browser sessions
    await vm.unexposePort(3000);
    await vm.updateNetworkPermissions(false, ["10.0.0.0/8"]);

    const forward = await vm.portForward(5432); // private localhost TCP port
    console.log(forward.host, forward.port, forward.url);
    await forward.close();
    const stream = await vm.tunnel(5432); // Node Duplex; authenticated WebSocket
    stream.write(Buffer.from("bytes"));
    stream.close();

    await vm.ssh(["uname", "-a"]); // omit command for an interactive shell
    await vm.scp("./file.txt", "vm:/workspace/file.txt");
    await vm.scp("vm:/workspace/file.txt", "./download.txt");
    const watcher = await vm.sync("./project", { watch: true });
    await watcher.close();
    ```

    `ports`/`protectedPorts` publish stable URLs; `privatePorts` and `bind(port)`
    allow authenticated TCP access without publishing. Terminal and desktop URLs
    are available as `terminalUrl` and `desktopUrl`; `listUrls()` refreshes published
    URLs. The browser access URL exchanges a short-lived credential for an HTTP-only
    cookie. Network restrictions persist across stop/start.

    Tunnels reconnect for up to two minutes and replay acknowledged requests without
    duplicating bytes. Port forwarding binds loopback by default; set `localPort`
    to choose a port. SSH/SCP require local OpenSSH; sync requires local rsync and
    supports `remotePath`, `exclude`, `watch` and `signal`. Generated SSH keys use
    the CLI's `~/.beam/ssh/vm_ed25519` identity; private keys stay local. Provide
    `sshPublicKey`/`sshKeyPath` for your own key. `VM.get` and `VM.connect` accept
    `{ sshKeyPath }` to use it when reattaching. Every local handle can be closed
    explicitly or through `vm.close()`.
  </Tab>
</Tabs>

## Desktop controls

<Tabs>
  <Tab title="Python">
    Create the VM with `desktop=True` to use the desktop APIs.

    ```python theme={null}
    from beam import VM

    with VM.connect("agent-workspace") as vm:
        png = vm.desktop.screenshot("./desktop.png")
        print(vm.desktop.screen_size())
        vm.desktop.resize(1280, 720)
        vm.desktop.move_mouse(100, 100)
        vm.desktop.click(x=100, y=100, button="left")
        vm.desktop.scroll(direction="down", amount=3)
        vm.desktop.press("ctrl+l")
        vm.desktop.write("https://example.com\n")
        vm.desktop.press("Return")
        vm.desktop.drag((100, 100), (200, 200))
        app = vm.desktop.launch("xterm")
        recording = vm.desktop.record("/workspace/screen.mp4", fps=30)
        vm.desktop.stop_recording(recording)
        vm.fs.download_file("/workspace/screen.mp4", "./screen.mp4")
    ```

    Screenshots return PNG bytes and can save to a local path. Text uses the
    clipboard for literal UTF-8. Recordings use guest CPU encoding and are
    finalized by `stop_recording()`. Resolution supports 320–4096 by 200–2160;
    recording supports 1–60 FPS. See the [desktop guide](/v2/vm/desktop).
  </Tab>

  <Tab title="JavaScript">
    `vm.desktop` supports `screenshot(localPath?)`, `screenSize()`,
    `resize(width, height)`, `moveMouse(x, y)`, `click({ x, y, button, count })`,
    `scroll({ direction, amount })`, `press(...keys)`, `write(text)`,
    `drag({ x, y }, { x, y })` and `launch(argv)`.

    ```javascript theme={null}
    await vm.desktop.press("ctrl+l");
    await vm.desktop.write("https://example.com\n");
    await vm.desktop.press("Return");
    const recording = await vm.desktop.record("/workspace/screen.mp4", { fps: 30 });
    await vm.desktop.stopRecording(recording);
    await vm.fs.downloadFile("/workspace/screen.mp4", "./screen.mp4");
    ```

    Screenshots return PNG bytes. Text is pasted through the clipboard to preserve
    literal UTF-8. Recordings use guest CPU encoding and are finalized with SIGINT.
    Resolution supports 320–4096 by 200–2160, and recordings support 1–60 FPS.
  </Tab>
</Tabs>

## API reference

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from beam import VM
    ```

    | Operation | SDK |
    | - | - |
    | Create / prepare | `VM(name, **options).create(wait=True)`; `prepare()` builds the launch request without starting compute. |
    | Resolve / connect / list | `VM.get(name)`, `VM.connect(name)`, `VM.list(all=False, metadata=None, status=None)`. Pass `context="CONTEXT"` when needed. |
    | Refresh / readiness | `vm.refresh()`, `vm.wait(timeout=180, services=False)`. |
    | Lifecycle | `vm.start(wait=True, cold=False)`, `vm.resume()`, `vm.stop(no_snapshot=False)`, `vm.pause()`, `vm.remove()`. |
    | Idle policy / metadata | `vm.update(ttl=..., idle_action=..., auto_resume=..., metadata=...)`. |
    | Activity / metrics | `with vm.keep_alive(): ...`; `vm.metrics()`. |
    | Processes / files / Docker | `vm.process`, `vm.fs`, `vm.docker`; async counterparts through `vm.aio`. |
    | Snapshot / fork | `vm.snapshot(name=None)`, `vm.snapshots()`, `vm.remove_snapshot(name)`, `vm.fork(name=None, wait=True)`. Restore with `VM(snapshot=ID).create()`. |
    | Template | `vm.create_template(name=None, description="")`; launch with `VM(template=NAME_OR_ID).create()`. |
    | Ports | `vm.expose(port, protected=None)`, `vm.unexpose(port)`, `vm.bind(port)`, `vm.get_url(port)`. |
    | Access | `vm.traffic_access_token`, `vm.rotate_access_token()`, `vm.access_url(port, ttl=600)`. |
    | Egress | `vm.update_network_permissions(block_network=False, allow_list=None)`. |
    | Desktop | `vm.desktop` controls; `vm.desktop_url`, `vm.terminal_url`. |
    | Client cleanup | `vm.close()` or a `with` block; does not stop the VM. |

    Use the `VM` lifecycle and port methods for the persistent resource. Its async process/filesystem/Docker interface reuses sandbox transports; sandbox termination and sandbox preview-URL methods are not replacements for VM lifecycle and stable URLs.
  </Tab>

  <Tab title="JavaScript">
    | CLI / Python capability | JavaScript |
    | - | - |
    | new, get, list, connect, prepare | VM.create, VM.get, VM.list, VM.connect, vm.prepare |
    | image build | VM.prepareImage plus Image.build |
    | update, network | vm.update, vm.updateNetworkPermissions |
    | start/resume, pause, stop, rm | vm.start/resume, vm.pause, vm.stop, vm.remove |
    | exec, ps, kill, process reattachment, stdin | vm.exec, vm.listProcesses, vm.getProcess, process.kill |
    | files and code execution | vm.fs, vm.runCode |
    | Docker | vm.docker |
    | snapshot create/list/rm, fork from snapshot | vm.snapshots and VM.snapshots; artifact.fork |
    | template create/list/show/rm, create from template | vm.templates and VM.templates; VM.create({ template }) |
    | fork current VM | vm.fork |
    | ports/expose/unexpose, access tokens and sessions | vm.listUrls/exposePort/unexposePort/getAccessToken/rotateAccessToken/accessUrl |
    | terminal and desktop links | vm.terminalUrl, vm.desktopUrl |
    | tunnel/port-forward, SSH/SCP | vm.tunnel/portForward, vm.ssh/scp |
    | sync/watch | vm.sync |
    | prompt and unit/session/process logs | vm.prompt, vm.logs |
    | metrics, activity/keep\_alive | vm.metrics, vm.touch, vm.keepAlive |
    | desktop screenshot/input/resize/launch/record | vm.desktop |

    `VMError` retains management HTTP `status` and the underlying `cause`. Only
    the backend's transient lifecycle-lock conflict is automatically retried;
    other conflicts, such as a duplicate name, remain visible to the caller.
  </Tab>
</Tabs>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.