# Networking

Every port in a sandbox can get a public HTTPS URL, and every sandbox is reachable over SSH through our gateway. Both wake a paused sandbox on the first request.

## Public URLs for ports

`POST /sandboxes/{sandbox_id}/ports` publishes a port at:

```
https://<short_id>-<port>.taskhost.cloud
```

`<short_id>` is the sandbox ID without the `sbx_` prefix (`sbx_a2b3c4d5e6f7` → `a2b3c4d5e6f7`): an underscore is not valid in hostnames. Use `port.url` or the sandbox's `url_template` instead of building URLs by hand.

HTTP/1.1, HTTP/2 and WebSocket are supported. TLS terminates at our edge; your server listens on plain HTTP inside the VM (bind to `0.0.0.0` or `127.0.0.1`).

Sandbox URLs live on `taskhost.cloud`, a registrable domain separate from `task.cloud` (website, API and docs). Code running in a sandbox is therefore never same-site with the API and cannot set cookies for `task.cloud`. `taskhost.cloud` is submitted to the [Public Suffix List](https://publicsuffix.org/) so that browsers also treat each sandbox hostname as a separate site from other sandboxes.

```ts
await sandbox.exec({ command: "npm run dev -- --port 3000", cwd: "/home/user/app", background: true });
const port = await sandbox.ports.expose({ port: 3000, visibility: "public" });
console.log(port.url); // https://a2b3c4d5e6f7-3000.taskhost.cloud
```

```python
sandbox.exec(command="npm run dev -- --port 3000", cwd="/home/user/app", background=True)
port = sandbox.ports.expose(port=3000, visibility="public")
print(port.url)
```

```bash
curl -sS -X POST https://api.task.cloud/v1/sandboxes/sbx_2fqxnt4bkq7m/ports \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
  -d '{"port":3000,"visibility":"public"}'

taskcloud port expose sbx_2fqxnt4bkq7m 3000 --public
```

You can also expose ports at creation time with `ports: [{"port": 3000, "visibility": "public"}]` (up to 20). List with `GET .../ports`, remove with `DELETE .../ports/{port}`. Exposing an already exposed port updates its settings. Target: the URL responds within 1 s of your server starting.

### Visibility

| `visibility` | Who can open the URL |
| --- | --- |
| `private` (default) | requests with an access token (header `X-Sandbox-Token` or query `sandbox_token`), or an API key with `sandboxes:read` |
| `public` | anyone with the URL |

For a private preview in your end user's browser, mint a short-lived **access token** instead of exposing an API key. It grants HTTP access to this sandbox's private ports only.

```ts
const { token, expires_at } = await sandbox.accessTokens.create({ ttl_seconds: 3600, ports: [3000] });
const previewUrl = `${sandbox.ports.url(3000)}/?sandbox_token=${token}`;
```

```python
t = sandbox.access_tokens.create(ttl_seconds=3600, ports=[3000])
preview_url = f"{sandbox.ports.url(3000)}/?sandbox_token={t.token}"
```

`ttl_seconds` ranges from 60 to 86400 (default 3600). The token is shown once.

### Wake on request

A request to the URL of a paused sandbox resumes it (target p95 < 800 ms) and is then served normally. Inbound traffic on exposed ports counts as activity, so an app with visitors stays awake, and it auto-pauses once traffic stops. See [Lifecycle](../concepts/lifecycle.md).

### Not yet supported

- Raw TCP/UDP ports (`protocol` other than `http`): **Coming soon**. Use SSH port forwarding meanwhile.
- Custom domains: not available.

## SSH

`POST /sandboxes/{sandbox_id}/ssh` returns connection details for the gateway `ssh.taskhost.cloud:22`. The gateway username is the sandbox ID.

Three ways to authenticate:

1. **Account SSH keys** (`POST /ssh-keys`): work for every sandbox in the account.
2. **A key for this sandbox only:** pass `public_key` in the request; valid for `ttl_seconds` (default 3600, max 604800).
3. **One-time password:** returned in `password` when you pass no key and the account has none.

```ts
const access = await sandbox.ssh({ public_key: "ssh-ed25519 AAAA... me@laptop", ttl_seconds: 3600 });
console.log(access.command); // ssh sbx_2fqxnt4bkq7m@ssh.taskhost.cloud
```

```python
access = sandbox.ssh(public_key=open(os.path.expanduser("~/.ssh/id_ed25519.pub")).read(), ttl_seconds=3600)
print(access.command)
```

```bash
curl -sS -X POST https://api.task.cloud/v1/sandboxes/sbx_2fqxnt4bkq7m/ssh \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
  -d "{\"public_key\":\"$(cat ~/.ssh/id_ed25519.pub)\"}"
# {"host":"ssh.taskhost.cloud","port":22,"username":"sbx_2fqxnt4bkq7m","password":null,"expires_at":"...","command":"ssh sbx_2fqxnt4bkq7m@ssh.taskhost.cloud","host_key_fingerprint":"SHA256:..."}

taskcloud ssh sbx_2fqxnt4bkq7m          # sends ~/.ssh/id_ed25519.pub as a temporary key and connects
```

The response also contains `host_key_fingerprint`; verify it on first connect. Log in as another Linux user with `user` (default `user`).

SSH supports everything OpenSSH does through the gateway:

```bash
ssh sbx_2fqxnt4bkq7m@ssh.taskhost.cloud 'uname -a'               # one command
ssh -L 5432:localhost:5432 sbx_2fqxnt4bkq7m@ssh.taskhost.cloud     # local forward
ssh -R 8080:localhost:8080 sbx_2fqxnt4bkq7m@ssh.taskhost.cloud     # reverse forward
scp ./data.csv sbx_2fqxnt4bkq7m@ssh.taskhost.cloud:/home/user/
```

VS Code and Cursor Remote-SSH work with a host entry:

```
Host sbx_2fqxnt4bkq7m
  HostName ssh.taskhost.cloud
  User sbx_2fqxnt4bkq7m
  IdentityFile ~/.ssh/id_ed25519
```

Connecting to a paused sandbox wakes it. An open SSH session counts as activity.

## Outbound traffic

Outbound internet access from the sandbox is open by default. An **egress allowlist** (restricting outbound traffic to listed domains) is **Coming soon**. Abuse such as mining, spam or scanning leads to suspension; see [Security](../security.md).

Inbound traffic reaches the VM only through exposed ports and the SSH gateway. Each sandbox has its own network namespace and cannot reach other sandboxes.

## Required scopes

| Operation | Scope |
| --- | --- |
| list ports | `sandboxes:read` |
| expose / remove ports, access tokens | `sandboxes:write` |
| SSH access, SSH keys | `ssh` |

## Related

- [Exec and files](exec-and-files.md)
- [Desktop and computer use](desktop-and-computer-use.md): the desktop has its own URL, `https://<short_id>-desktop.taskhost.cloud`
- [Security](../security.md)
- [Limits](../limits.md)
