Port forwarding
Reach any port your in-sandbox code is listening on over HTTPS. Authenticated and WebSocket-aware.
sandbox.url(port) returns a URL that proxies to whatever your sandbox is
listening on at that port. Start a vite dev server, a jupyter notebook, a
gradio app, or any HTTP server inside the sandbox and reach it over HTTPS.
The URL is authenticated, not public. It lives under /v1, so every
request needs your Authorization: Bearer <API key> header — a browser
opening it directly gets 401. Proxy it from your own backend, or attach the
header from your client, rather than handing the raw URL to an end user.
Preview a dev server
import { Isorun } from 'isorun'
const isorun = new Isorun()
const sandbox = await isorun.create({ image: 'node:22-slim' })
await sandbox.exec('git clone https://github.com/me/my-app && cd my-app && npm install')
// Start vite in the background. nohup + setsid + redirect so the exec
// call returns immediately and the server keeps running.
await sandbox.exec('cd my-app && nohup setsid npm run dev >/dev/null 2>&1 &')
// Reach the dev server through the runner.
const url = sandbox.url(5173)
console.log(url)
// → https://run-<region>.isorun.ai/v1/runs/<runID>/proxy/5173/
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ISORUN_API_KEY}` },
})URL format
The URL is a path on the runner that already serves your API:
<apiUrl>/v1/runs/<runID>/proxy/<port>/<path>?<query>| Component | Meaning |
|---|---|
<apiUrl> | Runner base URL, isorun.apiUrl — derived from your key's region |
<runID> | Sandbox ID returned by sandbox.id |
<port> | TCP port your in-sandbox server is listening on |
<path> | Forwarded verbatim to the in-sandbox server |
<query> | Forwarded verbatim |
So .../v1/runs/run0123456789abcdef/proxy/8000/foo/bar?q=1 becomes a request to
localhost:8000/foo/bar?q=1 inside the sandbox.
sandbox.url(port, path) takes an optional second argument for the path, so
sandbox.url(8000, '/foo/bar') builds the same URL for you.
What passes through the proxy
- All HTTP methods. GET, POST, PUT, DELETE, PATCH, anything.
- Streaming responses. SSE, chunked encoding, etc. pass through without buffering.
- WebSocket upgrades. Vite HMR, Jupyter kernel WS, hot-reload, notebook websockets, gradio's queue WS, langchain streaming, all work end-to-end.
- Cookies. Forwarded both directions.
- Large request and response bodies. No size cap from the proxy.
Authentication model
The URL is anonymous: anyone with the URL can hit it. The sandbox ID in the host is high-entropy, so it's effectively unguessable unless you share it.
The sandbox ID is the only credential on a public URL. Treat any URL you generate as a bearer secret: sharing it grants access to that port.
If you need stronger auth in front of your in-sandbox service, run it behind a reverse proxy with auth inside the sandbox itself, or ship a per-port allowlist via your own gateway.
What gets added to the forwarded request:
X-Forwarded-HostX-Forwarded-Proto: httpsX-Real-IP: <client IP>
Performance
Most of the round-trip is network distance between the client and the runner. For traffic that needs the lowest latency, hold a persistent connection to the URL: HTTP keep-alive, WebSocket, or streaming SSE.
Limits
| Constraint | Value |
|---|---|
| Ports per sandbox | any TCP port your code listens on |
| Method support | all HTTP methods including PATCH and CUSTOM |
| WebSocket support | yes, end-to-end |
| Auth | none; the sandbox ID is the credential |
Next steps
- Computer use (CDP + VNC), expose Chrome DevTools and noVNC through the same proxy.
- TypeScript SDK, the
url(port, path?)reference.