> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Ports and previews

Reach a web server running in a sandbox through a signed preview URL on usercontent.b3os.org, private by default or public, with base-path handling.

A server in a sandbox is reachable from outside only on ports you declare when you create the sandbox, through a preview URL on `usercontent.b3os.org`.

## Declare ports

```json
{ "template": "base", "ports": [{ "port": 3000 }, { "port": 8080, "public": true }] }
```

Up to 8 ports. A port you did not declare answers [`port_not_exposed`](/errors#port_not_exposed).

## Private ports

A port is private by default: nobody reaches it without a URL you mint for it. Ask for one when you need it:

**curl**

```bash
    curl -s -X POST "https://beta-api.b3os.org/v1/sandboxes/$SBX/ports/3000/url" \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"ttl_s": 600}'
    ```
  **TypeScript**

```ts
    const preview = await b3os.sandboxes.createPortUrl(sbx, 3000, { ttl_s: 600 });
    console.log(preview.url, preview.expires_at);
    ```
  **Python**

```python
    preview = b3os.sandboxes.create_port_url(sbx, 3000, {"ttl_s": 600})
    print(preview["url"], preview["expires_at"])
    ```
  **MCP**

```text
    Start the dev server in my sandbox on port 3000 in the background.
    ```

    The agent starts the server with `sandbox_exec`. The MCP server has no tool that mints preview URLs; mint one from code, or with `b3os sandbox url $SBX 3000`.

```json
{
  "object": "port_url",
  "port": 3000,
  "public": false,
  "url": "https://usercontent.b3os.org/sbx/sbx-01m5t68q1b590rj801nbzsqzkk/3000/pt1.n1.1792955460.tjsgjiRC3sSYEwneW62TSvNqd-Jo_zzFI5zFYkB_YQBNl0CiBzI9xgoV6EJD_5YEmh3HbzSmb3acL8mY1LmYCg/",
  "expires_at": "2026-10-25T19:11:00.000Z"
}
```

The token in the path is signed by the node and expires after `ttl_s` (10 minutes by default, from one minute to 24 hours). Anyone with the URL reaches the port until then, with no cookie or other credential, so share it like a password.

## Public ports

A port declared `public: true` gets a long-lived URL in the sandbox's `ports[].url`, ready as soon as the sandbox is. Its token lasts 7 days from the sandbox's creation, so the URL stays fixed; a sandbox that runs longer gets a new one.

## What your server sees

The preview forwards `GET` (WebSocket upgrades included), `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` and `OPTIONS`; other methods get `405`. Your server receives the request at the path after the token, with the query string unchanged, plus these headers:

| Header | Value |
|---|---|
| `X-Forwarded-Proto` | `https` |
| `X-Forwarded-Host` | `usercontent.b3os.org` |
| `X-Forwarded-Prefix` | `/sbx/<host_id>/<port>/<token>`, the full base path of this request |

Cookies are removed in both directions, as everywhere on `usercontent.b3os.org`. Unless your server sets `Referrer-Policy`, the preview adds `Referrer-Policy: strict-origin`, so the token in the path does not leak to other sites. A response must start within 65 seconds; a WebSocket or a streamed body can stay open up to an hour.

## Base paths

Your app lives under a path prefix, not at the root of a domain. Two rules keep links working:

- Emit relative URLs where you can. A URL that ends at the token redirects to the same URL plus `/`, so relative links resolve under the prefix.
- For absolute paths, build them from `X-Forwarded-Prefix` on each request. `B3OS_PORT_BASE_PATH` in the sandbox's environment is only the fixed start, `/sbx/<host_id>`, because the port and the token follow it and the token differs for every minted URL.

In v0.1 every preview shares the `usercontent.b3os.org` origin. A page served from one preview can send requests to any other preview URL it knows. Keep secrets out of previews. Per-port hostnames are planned.

## When it fails

| Code | Status | When |
|---|---|---|
| [`signed_url_invalid`](/errors#signed_url_invalid) | 403 | The token is malformed, expired, or for another sandbox or port |
| [`port_not_exposed`](/errors#port_not_exposed) | 404 | The port was not declared when the sandbox was created |
| [`sandbox_not_running`](/errors#sandbox_not_running) | 409 | The sandbox is paused or stopped |