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

# Preview a sandbox application

> Expose a web application running in a sandbox through a public or private HTTPS URL.

A preview routes an HTTPS URL to a port in your sandbox. Use it to open an
agent-generated website in a browser or share a running application. A sandbox
can have multiple previews, each with its own name and access settings.

Previews are private by default. A private preview requires a preview token;
a public preview lets anyone with the URL reach your application.

## Start an application and create a preview

[Set up a Baseten SDK](/sandboxes/manage.md#set-up) and
[create a sandbox](/sandboxes/manage.md#create-a-sandbox) before running these
examples. Set `BASETEN_API_KEY` for SDK authentication. The examples use an
existing sandbox named `hello-world` with Python installed.

Bind your application to the sandbox's `HOST` environment variable so the
preview can reach it. Port 80 and the sandbox's reserved system ports can't
serve previews. A preview on port 443 must stay private.

Run the following code to start a web server on port 3000 and create a private
preview named `web`. The process call waits for the server's port before
creating the preview:

**TypeScript**

**To open the application through a private preview**:

```typescript theme={"system"}
import { SandboxClient } from "@basetenlabs/sandbox";

const client = new SandboxClient({ apiKey: process.env.BASETEN_API_KEY! });
const sandbox = await client.get({ name: "hello-world" });

await sandbox.process.exec({
  command: 'python3 -m http.server 3000 --bind "$HOST" --directory /tmp',
  name: "preview-server",
  waitForPorts: [3000],
});
const preview = await sandbox.previews.create({
  name: "web",
  port: 3000,
  public: false,
});
const token = await sandbox.previews.createToken({
  previewName: preview.name,
  expiresAt: new Date(Date.now() + 3_600_000),
});
const url = new URL(preview.url);
url.searchParams.set("preview_token", token.token);
console.log(url.toString());
```

**Python**

**To open the application through a private preview**:

```python theme={"system"}
import os
from datetime import datetime, timedelta, timezone
from urllib.parse import urlencode

from baseten.sandbox import SandboxClient

with SandboxClient(api_key=os.environ["BASETEN_API_KEY"]) as client:
    sandbox = client.get(name="hello-world")
    sandbox.process.exec(
        command='python3 -m http.server 3000 --bind "$HOST" --directory /tmp',
        name="preview-server",
        wait_for_ports=[3000],
    )
    preview = sandbox.previews.create(name="web", port=3000, public=False)
    token = sandbox.previews.create_token(
        preview_name=preview.name,
        expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
    )
    query = urlencode({"preview_token": token.token})
    print(f"{preview.url}?{query}")
```

**Go**

**To open the application through a private preview**:

```go theme={"system"}
package main

import (
    "context"
    "fmt"
    "log"
    "net/url"
    "os"
    "time"

    "github.com/basetenlabs/baseten-go/sandbox"
)

func main() {
    ctx := context.Background()
    client, err := sandbox.NewClient(sandbox.ClientOptions{
        APIKey: os.Getenv("BASETEN_API_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }
    sb, err := client.Get(ctx, sandbox.GetOptions{Name: "hello-world"})
    if err != nil {
        log.Fatal(err)
    }
    if _, err := sb.Process().Exec(ctx, sandbox.ProcessExecOptions{
        Command:      `python3 -m http.server 3000 --bind "$HOST" --directory /tmp`,
        Name:         "preview-server",
        WaitForPorts: []int{3000},
    }); err != nil {
        log.Fatal(err)
    }
    preview, err := sb.Previews().Create(ctx, sandbox.PreviewCreateOptions{
        Name:   "web",
        Port:   3000,
        Public: false,
    })
    if err != nil {
        log.Fatal(err)
    }
    token, err := sb.Previews().CreateToken(ctx, sandbox.PreviewCreateTokenOptions{
        PreviewName: preview.Name,
        ExpiresAt:   time.Now().Add(time.Hour),
    })
    if err != nil {
        log.Fatal(err)
    }
    previewURL, err := url.Parse(preview.URL)
    if err != nil {
        log.Fatal(err)
    }
    query := previewURL.Query()
    query.Set("preview_token", token.Token)
    previewURL.RawQuery = query.Encode()
    fmt.Println(previewURL.String())
}
```

Open the printed URL in a browser. You should see the directory listing for
`/tmp`. For your own application, replace the server command and port. The
preview routes traffic to the running application; it doesn't start it.

Retrieve or create the sandbox through the SDK before managing its previews.
Preview operations need the sandbox's name and don't work on a handle created
from its URL alone.
On subsequent runs, reuse the running server and existing preview rather than
starting another process on the same port. To reuse a preview by name, pass
`createIfNotExists: true` in TypeScript, `create_if_not_exists=True` in Python,
or `CreateIfNotExists: true` in Go.
This returns the existing preview unchanged, including its access settings.

## Control preview access

The preview token in the example expires after 1 hour. It grants access to
that private preview and is separate from the
[sandbox access token](/sandboxes/authentication.md) used to manage resources.
Treat a URL containing a preview token as a credential and share it only with
people who should have access. The first request sets a cookie so the browser
can continue navigating without the query parameter.

To let anyone with the URL open the application, make the preview public:

**TypeScript**

**To allow access without a token**:

```typescript theme={"system"}
import { SandboxClient } from "@basetenlabs/sandbox";

const client = new SandboxClient({ apiKey: process.env.BASETEN_API_KEY! });
const sandbox = await client.get({ name: "hello-world" });

const publicPreview = await sandbox.previews.update({ name: "web", public: true });
console.log(publicPreview.url);
```

**Python**

**To allow access without a token**:

```python theme={"system"}
import os

from baseten.sandbox import SandboxClient

with SandboxClient(api_key=os.environ["BASETEN_API_KEY"]) as client:
    sandbox = client.get(name="hello-world")
    preview = sandbox.previews.update(name="web", public=True)
    print(preview.url)
```

**Go**

**To allow access without a token**:

```go theme={"system"}
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/basetenlabs/baseten-go/sandbox"
)

func main() {
    ctx := context.Background()
    client, err := sandbox.NewClient(sandbox.ClientOptions{
        APIKey: os.Getenv("BASETEN_API_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }
    sb, err := client.Get(ctx, sandbox.GetOptions{Name: "hello-world"})
    if err != nil {
        log.Fatal(err)
    }
    public := true
    preview, err := sb.Previews().Update(ctx, sandbox.PreviewUpdateOptions{
        Name:   "web",
        Public: &public,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(preview.URL)
}
```

Open the returned URL in a new private browser window to verify access without
a token. Set `public` to `false` in TypeScript and Go or `False` in Python to require
a token again.

## Set an expiration

A preview's expiration controls when its URL stops serving. It's separate
from its tokens' expiration and the [sandbox's lifetime](/sandboxes/lifecycle.md).
To delete the preview after 1 hour, set a relative lifetime when creating or
updating it:

**TypeScript**

**To expire the preview after 1 hour**:

```typescript theme={"system"}
import { SandboxClient } from "@basetenlabs/sandbox";

const client = new SandboxClient({ apiKey: process.env.BASETEN_API_KEY! });
const sandbox = await client.get({ name: "hello-world" });

const expiringPreview = await sandbox.previews.update({
  name: "web",
  ttlMs: 3_600_000,
});
console.log(expiringPreview.expiresAt);
```

**Python**

**To expire the preview after 1 hour**:

```python theme={"system"}
import os
from datetime import timedelta

from baseten.sandbox import SandboxClient

with SandboxClient(api_key=os.environ["BASETEN_API_KEY"]) as client:
    sandbox = client.get(name="hello-world")
    preview = sandbox.previews.update(name="web", ttl=timedelta(hours=1))
    print(preview.expires_at)
```

**Go**

**To expire the preview after 1 hour**:

```go theme={"system"}
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/basetenlabs/baseten-go/sandbox"
)

func main() {
    ctx := context.Background()
    client, err := sandbox.NewClient(sandbox.ClientOptions{
        APIKey: os.Getenv("BASETEN_API_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }
    sb, err := client.Get(ctx, sandbox.GetOptions{Name: "hello-world"})
    if err != nil {
        log.Fatal(err)
    }
    preview, err := sb.Previews().Update(ctx, sandbox.PreviewUpdateOptions{
        Name: "web",
        TTL:  time.Hour,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(preview.ExpiresAt.Format(time.RFC3339))
}
```

Check that the printed expiration is 1 hour after the update request.
You can set an absolute expiration
with `expiresAt` in TypeScript, `expires_at` in Python, or `ExpiresAt` in Go instead. Use either a
relative lifetime or an absolute expiration in one request.

## Inspect previews with the CLI

[Install the Baseten CLI and sign in](/sandboxes/manage.md#set-up) before using
CLI commands. To list previews and inspect a preview's URL, port, and status,
run:

```bash theme={"system"}
baseten sandbox preview list --name hello-world
baseten sandbox preview describe --name hello-world --preview-name web
```

The list includes each preview's name, port, access setting, status, URL, and
expiration. If a URL returns a `502` error, check that your application is
running on the preview's port and listening on `HOST`.

## Remove a preview

Deleting a preview removes its tokens and stops access through its URL. It
leaves the sandbox running. Deleting the sandbox also removes its previews.

Run the following code or commands to delete the `web` preview and stop the
example server:

**TypeScript**

**To remove the preview and stop the server**:

```typescript theme={"system"}
import { SandboxClient } from "@basetenlabs/sandbox";

const client = new SandboxClient({ apiKey: process.env.BASETEN_API_KEY! });
const sandbox = await client.get({ name: "hello-world" });

await sandbox.previews.delete({ name: "web" });
await sandbox.process.kill({ identifier: "preview-server" });
```

**Python**

**To remove the preview and stop the server**:

```python theme={"system"}
import os

from baseten.sandbox import SandboxClient

with SandboxClient(api_key=os.environ["BASETEN_API_KEY"]) as client:
    sandbox = client.get(name="hello-world")
    sandbox.previews.delete(name="web")
    sandbox.process.kill(identifier="preview-server")
```

**Go**

**To remove the preview and stop the server**:

```go theme={"system"}
package main

import (
    "context"
    "log"
    "os"

    "github.com/basetenlabs/baseten-go/sandbox"
)

func main() {
    ctx := context.Background()
    client, err := sandbox.NewClient(sandbox.ClientOptions{
        APIKey: os.Getenv("BASETEN_API_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }
    sb, err := client.Get(ctx, sandbox.GetOptions{Name: "hello-world"})
    if err != nil {
        log.Fatal(err)
    }
    if _, err := sb.Previews().Delete(ctx, sandbox.PreviewDeleteOptions{Name: "web"}); err != nil {
        log.Fatal(err)
    }
    if err := sb.Process().Kill(ctx, sandbox.ProcessKillOptions{Identifier: "preview-server"}); err != nil {
        log.Fatal(err)
    }
}
```

**CLI**

**To remove the preview and stop the server**:

[Install the Baseten CLI and sign in](/sandboxes/manage.md#set-up) if you
haven't already.

1. Delete the preview:

   ```bash theme={"system"}
   baseten sandbox preview delete --name hello-world --preview-name web
   ```

2. Stop the example server:

   ```bash theme={"system"}
   baseten sandbox process kill --name hello-world --process-name preview-server
   ```

Open the preview URL in a new browser window to confirm that it no longer
serves your application.
