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

# Expose a preview

> Publish a port running inside a sandbox as an HTTPS URL

If your sandbox is running a dev server or API on some port, a preview turns that port into an HTTPS URL a browser can open. See [Previews](https://docs.bonya.ai/concepts/previews.md) for the full field reference; this guide walks through creating one, choosing an auth mode, and cleaning up.

## Create a preview

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 3000, "name": "web"}'
```

```shell CLI theme={null}
bonya preview create $SANDBOX_ID 3000 --name web
```

```ts TypeScript theme={null}
const preview = await sandbox.createPreview(3000, { name: "web" });
console.log(preview.url); // https://pv-<26 chars>.tv.bonya.ai
```

```python Python theme={null}
preview = sandbox.create_preview(3000, name="web")
print(preview.url)
```

```go Go theme={null}
preview, err := sandbox.CreatePreview(ctx, 3000, bonya.CreatePreviewOptions{Name: "web"})
fmt.Println(preview.URL)
```

`port` must be 1024–65535 — privileged ports are never previewable. Create returns `201` with the created preview plus a `capability_jws` — that token is returned **only on create**, never again from list, so hold onto it if you need the token-mode exchange below.

## Token vs. public auth

`auth_mode` (REST) / `auth` (SDK) is one of two values:

* **`token`** (default) — access is gated by a one-time browser exchange (below). This is what you want for anything not meant to be open to the internet.
* **`public`** — anyone with the URL can view it, no credential at all. The only thing protecting a public preview is the 26 random characters in its hostname. Use this deliberately, for demos or sharing outside your organization — not as the default.

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 8080, "auth_mode": "public"}'
```

```shell CLI theme={null}
bonya preview create $SANDBOX_ID 8080 --auth public
```

```ts TypeScript theme={null}
import { PreviewAuth } from "@bonya-ai/bonya";

const publicPreview = await sandbox.createPreview(8080, { auth: PreviewAuth.PUBLIC });
```

```python Python theme={null}
public = sandbox.create_preview(8080, auth=PreviewAuth.PUBLIC)
```

The CLI marks public preview URLs in its output as a reminder of what you just did.

## The token-mode browser exchange

In `token` mode, a browser needs to trade the `capability_jws` for a session cookie once before it can view the preview. At a high level: navigate to `preview.url` with `?bonya_token=<capability_jws>` appended, and Roxy (the preview proxy) validates the token, sets a host-scoped `Secure`/`HttpOnly` cookie, and redirects to the same URL with the token stripped — before any guest content renders. See [Previews](https://docs.bonya.ai/concepts/previews.md#the-token-mode-browser-exchange) for the exact mechanics.

You don't have to build that URL by hand — the TypeScript, Python, and Go SDKs each expose a helper that does it for you:

```ts TypeScript theme={null}
const browserUrl = sandbox.previewBrowserUrl(preview);
// open browserUrl in a browser — one-time entry point
```

```python Python theme={null}
browser_url = sandbox.preview_browser_url(preview)
```

```go Go theme={null}
browserURL := sandbox.PreviewBrowserURL(preview)
```

This throws/raises/errors if called on a public preview, since there's no token to exchange.

> [!WARNING]
>
> Don't store `capability_jws` in a URL, log, or anywhere durable beyond the single navigation described above — it's a bearer credential for the preview.

## List and delete

```shell cURL theme={null}
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews" \
  -H "Authorization: Bearer $BONYA_API_KEY"

curl -s -X DELETE "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews/$PREVIEW_ID" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya preview list $SANDBOX_ID
bonya preview delete $SANDBOX_ID $PREVIEW_ID
```

```ts TypeScript theme={null}
await sandbox.listPreviews();
await sandbox.deletePreview(preview.id);
```

```python Python theme={null}
sandbox.list_previews()
sandbox.delete_preview(preview.id)
```

List never includes `capability_jws` — only create does. Delete is idempotent and deliberately doesn't distinguish "already deleted" from "belongs to another organization," so it always returns `204`.

## Related

> [card] [Previews](https://docs.bonya.ai/concepts/previews.md)

> [card] [Sandboxes](https://docs.bonya.ai/concepts/sandboxes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.