> ## 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.

# Previews

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

A **preview** exposes a port inside a sandbox as an HTTPS URL, so you can view a dev server, API, or any other listener from a browser.

## Creating 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);
```

```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)
```

**MCP:** the `preview_create` tool exposes this, with `capability_jws` deliberately dropped from the result — see below.

Request body (`PreviewCreateRequest`):

**port** `integer` *required*

Port to expose, 1024–65535.

**auth\_mode** `string` (default: token)

`token` or `public` (see below).

**name** `string`

Display metadata only, at most 80 UTF-8 bytes.

Create also accepts an `Idempotency-Key` header — see [Idempotency](https://docs.bonya.ai/concepts/idempotency.md).

Returns `201` with `PreviewCreateResponse`:

**preview** `object` *required*

The created `Preview` (see fields below).

**capability\_jws** `string` *required*

A capability token for the token-mode browser exchange described below.

> [!WARNING]
>
> `capability_jws` is returned **only by create** — it is never included in the list response. Store it if you need the token-mode exchange; there's no way to retrieve it again later. This is a deliberate security property, not an oversight.

### Preview fields

**preview\_id** `string` *required*

Matches `^pv-[a-z2-7]{26}$`.

**sandbox\_id** `string` *required*

The owning sandbox.

**port** `integer` *required*

The exposed port, 1024–65535.

**auth\_mode** `string` *required*

`token` or `public`.

**name** `string`

Display metadata only, at most 80 UTF-8 bytes.

**url** `string` *required*

Token-free base preview URL.

**created\_at\_unix\_nanos** `integer` *required*

Creation time.

## Auth modes

`auth_mode` is one of two values — not `private`/`public`:

* **`token`** (default) — access is gated by the Roxy browser exchange described below.
* **`public`** — anyone with the URL can view it; no exchange required.

### The token-mode browser exchange

In `token` mode, the client navigates once to `preview.url` with `?bonya_token=<capability_jws>` appended. Roxy (the preview proxy) validates the token, sets a host-scoped `Secure`, `HttpOnly` cookie, and redirects to the same URL with `bonya_token` stripped — before any guest content is rendered. After that one exchange, the cookie carries the session; the token itself should not be stored in URLs or logs beyond that single navigation.

Each SDK wraps this exchange in a `previewBrowserUrl`-style helper, which builds the one-time URL from the `capability_jws` returned by create. There's no cURL or CLI example for this step — it's a browser navigation, not a request/response call — and no MCP tool, since `preview_create`'s MCP result deliberately omits `capability_jws` in the first place.

```ts TypeScript theme={null}
const url = sandbox.previewBrowserUrl(preview);
// open `url` in a browser
```

```python Python theme={null}
import webbrowser
webbrowser.open(sandbox.preview_browser_url(preview))
```

```go Go theme={null}
url, err := sandbox.PreviewBrowserURL(preview)
// open url in a browser
```

Open it once and let the cookie carry the session — the URL itself should not be shared, since anyone who receives it holds the sandbox's data-plane capability until it expires. It throws/raises/errors (per SDK) on a public preview, which has no token to exchange.

## Listing previews

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

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

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

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

```go Go theme={null}
previews, err := sandbox.ListPreviews(ctx)
```

**MCP:** the `preview_list` tool covers this.

Returns `PreviewListResponse`: `{ "previews": [ ...Preview ] }`. As noted above, list **never** includes `capability_jws` — only create does.

## Deleting a preview

```shell cURL theme={null}
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 delete $SANDBOX_ID $PREVIEW_ID
```

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

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

```go Go theme={null}
err = sandbox.DeletePreview(ctx, preview.ID)
```

**MCP:** the `preview_delete` tool covers this, and is likewise idempotent.

> [!NOTE]
>
> Delete is idempotent and deliberately avoids leaking cross-org existence — deleting a preview that doesn't exist (or belongs to another organization) behaves the same as deleting one that does, rather than distinguishing the two cases.

Returns `204` on success.

## Related

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

> [card] [Idempotency](https://docs.bonya.ai/concepts/idempotency.md)


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