# Previews (/docs/sdk/previews)



`client.previews` exposes an HTTP service running inside a sandbox at an
authenticated public URL like `https://p-a1b2c3.crownest.dev`.
Preview access requires authentication in v1: API-key access by default or
a private token link for browser handoff. CrowNest strips its own cookies,
the `Authorization` header, and `Referer` before proxying requests to your
app.

A [`SandboxHandle`](/docs/sdk) exposes `sandbox.previews.create` and
`sandbox.previews.list` without the `sandboxId` argument.

## create [#create]

Expose a port from the sandbox. One active preview exists per sandbox and
port; creating a preview for the same port again returns the existing one.

```ts
create(
  sandboxId: `sbx_${string}`,
  input: { authMode?: "authenticated" | "token"; port: number },
): Promise<CreatePreviewResponse>
```

| Field      | Type                           | Default           | Description                                               |
| ---------- | ------------------------------ | ----------------- | --------------------------------------------------------- |
| `port`     | `number`                       | required          | Port the service listens on inside the sandbox (1–65535). |
| `authMode` | `"authenticated"` \| `"token"` | `"authenticated"` | API-key access by default, or token browser access.       |

```ts
await sandbox.commands.run("python3 -m http.server 8000", {
  background: true,
});

const { preview } = await client.previews.create(sandbox.id, { port: 8000 });
console.log(preview.url); // https://p-a1b2c3.crownest.dev
```

Create a token-mode preview instead when you need a browser-openable
private link:

```ts
const { preview: tokenPreview, previewToken } = await sandbox.previews.create({
  authMode: "token",
  port: 8000,
});
console.log(`${tokenPreview.url}?cn_token=${previewToken}`);
```

`CreatePreviewResponse` contains `{ preview, previewToken? }`.
`previewToken` is returned only when a new token-mode preview is created;
reads, lists, and deduped creates cannot recover it.

A sandbox can hold up to 3 active previews, and an org up to 10. Creating
one past either limit fails with `quota_exceeded`.

Error codes: `invalid_request`, `not_found`, `sandbox_destroyed`,
`quota_exceeded`. If the service behind the preview stops responding,
requests to the preview URL fail with `preview_unavailable`.

## list [#list]

List previews for a sandbox.

```ts
list(sandboxId: `sbx_${string}`): Promise<ListPreviewsResponse>
```

```ts
const previews = await client.previews.list(sandbox.id);
for (const preview of previews.data) {
  console.log(preview.url);
}
```

## get [#get]

Fetch a single preview by ID.

```ts
get(previewId: `prv_${string}`): Promise<Preview>
```

```ts
const preview = await client.previews.get("prv_abc123");
```

Error codes: `not_found`, `forbidden`.

## revoke [#revoke]

Revoke a preview so its URL stops serving traffic. The operation is
idempotent and returns the preview record with `revokedAt` set.

```ts
revoke(previewId: `prv_${string}`): Promise<Preview>
```

```ts
await client.previews.revoke(preview.id);
```

Error codes: `not_found`, `forbidden`.

## Preview object [#preview-object]

The `Preview` type includes `id` (`prv_` prefix), `slug` (DNS-safe, like
`p-a1b2c3`), `url`, `orgId`, `projectId`, `sandboxId`, `port`, `authMode`
(`authenticated` or `token`), `createdAt`, `expiresAt`, and `revokedAt`.

Previews end with their sandbox: when the sandbox is destroyed, the
preview URL stops working.

## Next steps [#next-steps]

* [Commands](/docs/sdk/commands) — start the service the preview points
  at.
* [Previews concept](/docs/concepts/previews) — authentication and proxy
  behavior.
* [Errors](/docs/sdk/errors) — error codes and retry guidance.
