# How to expose a port from a sandbox at an HTTPS URL Start the server with `spawn`. Then call `sbx.previews.create(port)`. The address needs a token, unless you pass `visibility: "public"`. **On Runtime, a shared port is private by default. It lives on its own domain, `runtimehost.com`. A visit wakes a paused sandbox.** So a preview can sit idle for days at paused-storage prices: $0.08 per decimal GB of saved state a month. Keeping a running machine instead costs $0.03125 an hour for 2 vCPU and 4 GiB (rates checked 25 October 2026, [pricing](/docs/pricing)). Runtime adds no badge or banner to the pages a preview serves. ## Share a port and call it ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.spawn("python3 -m http.server 3000"); const preview = await sbx.previews.create(3000); const page = await fetch(preview.url, { headers: { "x-runtime-preview-token": preview.token! }, }); console.log(page.status); // 200 ``` ```python check import urllib.request from withruntime import Sandbox with Sandbox.create() as sbx: sbx.spawn("python3 -m http.server 3000") preview = sbx.previews.create(3000) request = urllib.request.Request(preview["url"], headers={"x-runtime-preview-token": preview["token"]}) print(urllib.request.urlopen(request).status) # 200 ``` ```bash no-run runtime sandboxes preview "${id}" 3000 # private, with a token runtime sandboxes preview "${id}" 3000 --public # anyone with the address (paid) runtime sandboxes previews "${id}" runtime sandboxes unshare "${id}" 3000 ``` An agent on MCP uses `runtime_sandboxes_previews` with `create`, `list`, `rotate` and `delete`. ## Who can open it | How you share it | Who gets in | Send | | ------------------ | ----------------------------- | ---------------------------------- | | Private, from code | A client that sends the token | `x-runtime-preview-token: ` | | Private, by link | Whoever holds the link | `preview.urlWithToken` | | Public | Anyone with the address | `preview.url` | Public addresses are for paid sandboxes in an account that has bought credit. A sandbox without credit shares a port by link, with its token. While an account is blocked, or while every purchase it made is refunded in full, a public address needs the token too. It opens to anyone again once the account pays. To show a private preview inside your own app, set an iframe's `src` to `urlWithToken`. The browser keeps the token for that frame on your site only. So the page's scripts, styles and live reload load without it. To stop other sites from framing the preview, name your site when you share the port: `previews.create(3000, { embedOrigins: ["https://app.example.com"] })`. In Python, use `embed_origins`. You can list up to 16, and `https://*.example.com` covers subdomains. Each response then tells the browser to draw the preview only inside those sites. The link's token works in an iframe only there. With none named, any site may embed the preview. Making a port public or private is written to the account's [audit log](/docs/teams#audit-log). The entry records who did it and from where. ## Manage the tokens - **Revoke every token at the same time** with `previews.rotate(port)`. Every token issued before the call is refused. Use it when a link went somewhere it should not. - **Stop sharing** with `previews.delete(port)`. - **List what is shared** with `runtime sandboxes previews `. A private preview's token expires. So a link sent today does not work forever ([network access](/docs/security#network-access)). ## What the server must do - **Run under `spawn`, not `exec`.** Everything that `exec` starts ends when its command does. That includes `nohup … &`. `spawn` keeps the server running after your call returns. - **Listen on `0.0.0.0` or `localhost`** inside the sandbox. - **WebSockets work**, so dev servers keep their live reload. ## A paused sandbox wakes on a visit A visit to a shared port wakes a paused sandbox that has automatic wake turned on. An API client's request waits up to 30 seconds for the wake. A browser shows a short "Waking up" page that reloads itself. The wake is an ordinary wake, billed from the moment it runs. Pair a preview with `idlePauseSeconds`. That setting counts preview requests as use, so the sandbox runs only while people use it ([pause and resume](/how-to/pause-and-resume-a-sandbox)). ## Preview, port-forward, SSH or a domain? | You need | Use | | ----------------------------------------------- | ------------------------------------------------------- | | A URL for a person or a webhook | A preview | | The port on your own machine, just for you | `runtime sandboxes port-forward 3000` | | A shell or an editor in the sandbox | `runtime sandboxes ssh ` ([editors](/docs/editors)) | | Your own hostname, paid accounts | `runtime domains add app.example.com 3000` | | A raw TCP port, such as Postgres, paid accounts | `runtime ports open 5432` | Port forwarding and SSH go through Runtime's API with your key. So the sandbox opens nothing to the internet ([networking](/docs/networking)). ## Mistakes to avoid - **Putting the token in a shared URL by hand.** Send it as a header from code. Or hand out `urlWithToken`, which works in a browser, with `fetch`, `curl` and WebSocket clients. - **Making a preview public to skip the token.** A public address is open to anyone who finds it. Keep it private unless it is meant as a demo. - **Serving from `exec`.** The server ends when the command returns. Then nothing listens behind the preview. - **Expecting `previews_unavailable` to clear.** That 501 means previews are turned off there on purpose. Retrying will not help. - **Treating the address as your site.** Preview addresses sit under `runtimehost.com`, never under `withruntime.com`, so a sandbox's pages never share an origin with your account. For a full product built on previews, see [preview apps an AI agent builds](/use-cases/preview-agent-built-apps); for browser automation against a preview, see [Playwright](/integrations/playwright). Facts on this page were checked on 26 October 2026.