Preview URLs
Expose a port from a sandbox to the public internet with a stable, tokenless URL.
A preview URL is a public hostname that proxies HTTP(S) into a port inside your sandbox. It's how you let a teammate poke at a next dev server, share a Streamlit app with a stakeholder, or hand a webhook URL to a third-party service — without exposing your API key.
The URL is stable and tokenless: the sandbox UUID in the hostname is the credential. Anyone with the link can reach the port until the sandbox is killed. It runs behind the same TLS-terminating edge as the rest of the platform, so your sandbox never needs a public IP.
| How it's auth'd | The sandbox UUID is the bearer credential. |
| Who can reach it | Anyone with the URL. |
| Lifetime | Until the sandbox is killed. |
| Hostname shape | https://{port}-{sandboxId}.pandastack.ai |
| Use it for | Dev servers, demos, webhooks, multi-port apps. |
Treat the preview URL itself as a secret. Anyone who has it can reach that port for the sandbox's lifetime — there is no separate token to revoke, so kill the sandbox (or stop the server on that port) to cut access.
Public preview URLs
If your template runs a server on a port, that port is reachable instantly. Apps deployed on the base runtime start their dev/start command via the pandastack-autostart systemd unit, so the URL works the moment the sandbox is running.
const sandbox = await Sandbox.create({ template: "base" });
console.log(sandbox.previewUrl(3000));
// → https://3000-1a2b3c4d.pandastack.aisandbox = Sandbox.create(template="base")
print(sandbox.preview_url(3000))
# → https://3000-1a2b3c4d.pandastack.aipreviewUrl(port) is a pure string-formatter — no API call. The hostname is derived from your client's apiUrl:
apiUrl | Preview suffix |
|---|---|
https://api.pandastack.ai | pandastack.ai |
https://api.acme.dev | acme.dev |
| (self-hosted) | Set client.previewHost = "preview.example.com" |
Listing all exposed ports
const urls = await sandbox.previewUrls();
// { 3000: "https://3000-…", 9229: "https://9229-…" }urls = sandbox.preview_urls()
# {3000: "https://3000-…", 9229: "https://9229-…"}The SDK calls GET /v1/sandboxes/{id}/ports — the in-guest agent reports every listening TCP socket it sees.
Concurrency and rate limits
- A sandbox can expose up to 32 distinct ports simultaneously. Beyond that, the agent stops registering new ports until older ones close.
- Each preview hostname carries the same per-key rate limit as the rest of the API (1 req/s burst 10 by default). Override per-org via the cloud dashboard.
- WebSockets are supported transparently — useful for
next devHMR, Jupyter, and Streamlit.
REST equivalent
GET /v1/sandboxes/{id}/portsReturns {"ports": [{"port": 3000}, {"port": 9229}]} or the bare array form. Build the URL yourself as https://{port}-{id}.{suffix}.
How it works (one paragraph)
The TLS edge runs as a small Go service in front of every host. When a request arrives at https://3000-{id}.pandastack.ai, the edge extracts id, looks up the host that owns that sandbox in the shared state store, and proxies the TCP connection over the host's tap interface to the guest's NAT IP. No request ever touches a sandbox that doesn't match the hostname.
Ephemeral environments
Apps and managed databases share one lifecycle — create, serve, scale-to-zero on idle, wake on demand, delete. What's guaranteed at each step, and when to reach for an ephemeral environment versus an always-on one.
Snapshots and forks
Capture VM state in seconds. Fork running VMs in milliseconds.