# Ports, SSH and the desktop

> Reach a service on a zavu.run URL, connect over SSH, or open the machine screen in your browser

## Ports

Start a server inside the sandbox, listening on `0.0.0.0`, then give its port a URL:

```typescript
await zavu.sandboxes.commands.run(sandboxId, {
  command: "python3 -m http.server 3000",
  background: true,
});

const { port } = await zavu.sandboxes.ports.expose(sandboxId, { port: 3000 });
console.log(port.url); // https://k3x7…q2.zavu.run/?_token=…
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/ports \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 3000}'
```

The URL is on `zavu.run`. HTTP and WebSockets both go through, so a dev server with hot reload works.

- **Private (default).** The URL carries an access token in its query string: anyone holding the full URL can open it, so treat it as a secret. In a browser the token moves into a cookie for that host on the first visit and leaves the address bar, so the page's own links, assets and WebSockets work. Other clients send `?_token=` on every request.
- **Public.** Pass `public: true` for a URL anyone can open. Calling again with the other value switches the same URL.

Asking again for the same port returns the same URL. It survives a stop and resume: while the sandbox is stopped it answers `503`, and once the sandbox is deleted it answers `404`. A sandbox exposes at most 20 ports, and `zavu.sandboxes.ports.list(sandboxId)` lists them.

## SSH

Authorize your public key on the running machine. The answer carries the command to connect; you log in as its user, which has passwordless sudo.

```typescript
const { ssh } = await zavu.sandboxes.ssh.authorize(sandboxId, {
  publicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGq user@laptop",
});
console.log(ssh.command); // ssh user@203.0.113.10
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/ssh \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"publicKey\": \"$(cat ~/.ssh/id_ed25519.pub)\"}"
```

Send the one line in your `.pub` file. A private key is refused with `400`. The address changes after a stop and resume: authorize the key again then. This also works from an editor that opens remote folders over SSH.

## The desktop

Every sandbox has a graphical desktop with Chrome. Open it to watch an agent drive the browser, or to use the machine yourself.

```typescript
const { desktop } = await zavu.sandboxes.desktop.open(sandboxId, { view: "browser" });
console.log(desktop.url);
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/desktop \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"view": "browser"}'
```

`view` is `desktop` for the whole screen (the default) or `browser` for Chrome alone. The answer is a short-lived URL on `zavu.run` that streams the screen, mouse and keyboard included:

- Open it within a few minutes. Anyone holding it can use the machine until the sandbox stops, so treat it as a secret.
- The first request after a start can answer `409 sandbox_not_ready` for a few seconds while the stream is prepared. Retry.
- Ask for a new one after a resume.
