Preview URLs
A URL onto a port inside a sandbox, with a credential that can be rotated.
An agent that starts a dev server in its sandbox can publish the port, and what comes back is a URL somebody can open.
https://pv_01J8XYZ--5173.preview.example.com
└────────┘ └──┘
preview id portThe agent does it with the port_expose sandbox tool; you do it with the API. Both
reach the same code, so a model-minted preview and an operator-minted one are one
grant and one URL.
const { url, token, expiresAt } = await client.previews.mint({
sessionId,
port: 5173,
identity: { scope: "user", id: "u_412" } // optional: binds the URL to one person
})
await client.previews.list({ sessionId }) // port, url, expiry, revoked, public?
await client.previews.revoke({ id })mint costs data and revoke costs control: publishing a port is something an
agent does in the course of its work, and taking one away is an operator action.
The credential is not in the hostname
The host carries identity; the request carries the credential.
| Where | When |
|---|---|
?plane_preview=<jws> | The first navigation. Exchanged for a cookie and dropped. |
plane_preview cookie | Everything after — subresources, XHR, the WebSocket. |
Authorization: Bearer <jws> | A programmatic client. |
The one-shot query parameter is exchanged for a host-only, HttpOnly, Secure,
SameSite=Lax cookie by a 302 that drops the parameter, so a subresource
authenticates by cookie and the token never lands in history, a Referer, or a proxy
log. The cookie carries no Domain, so each preview host is its own cookie jar.
A token in the hostname was the obvious alternative and is wrong: a hostname travels in cleartext TLS SNI, is logged by every resolver between the viewer and you, cannot be rotated without breaking the link somebody has open, and would have to fit in the 63 characters a wildcard certificate covers.
sandbox.preview.auth: "public" skips all of it — a setting you opt into on purpose,
and previews.list shows which grants are public.
A grant outlives its tokens
The URL is stable and the credential rotates. A grant lasts a day by default; a token an hour. Revocation is the grant row, which the route re-reads every few seconds, so a revoke takes effect within that window.
A preview never wakes a machine
A parked sandbox is brought back by a sandbox tool call — a decision the agent makes inside a turn somebody is paying for. A preview request may arrive from a browser at three in the morning, so it is refused with the state named:
PreviewUnavailable: this session's sandbox is snapshotted, so nothing is listening on
port 5173. It comes back on the next sandbox tool call.Releasing a machine revokes its grants, because a snapshot restores a filesystem and not the process that was listening — a URL that says it is gone is a better answer than one that hangs against a tunnel nothing is behind.
The good part falls out of the key: a resume keeps the same sandbox id, and a grant is
keyed (sandbox, port, identity), so the next port_expose on that port re-opens the
same row and the link somebody left open starts working again. A park and a resume is
the shape of a lunch break.
Operator setup
Previews need PLANE_PREVIEW_DOMAIN, wildcard DNS and a wildcard certificate. Unset,
previews are disabled entirely and port_expose falls back to the provider's own URL.
See self-hosting.