# Previews (/docs/api/previews)



A preview exposes an HTTP service listening on a port inside a sandbox at
a stable HTTPS URL. In v1, all previews are private: use API-key
authenticated access by default, or token-mode browser links for handoff.
There are no public preview URLs.

## The Preview object [#the-preview-object]

| Field       | Type    | Description                                  |
| ----------- | ------- | -------------------------------------------- |
| `id`        | string  | Preview ID, prefixed `prv_`                  |
| `slug`      | string  | DNS-safe slug, such as `p-a1b2c3`            |
| `url`       | string  | `https://p-a1b2c3.crownest.dev`              |
| `orgId`     | string  | Owning organization, prefixed `org_`         |
| `projectId` | string  | Owning project, prefixed `prj_`              |
| `sandboxId` | string  | Sandbox the service runs in, prefixed `sbx_` |
| `port`      | integer | Port the sandbox service listens on          |
| `authMode`  | string  | `authenticated` or `token`                   |
| `createdAt` | string  | ISO 8601 creation timestamp                  |
| `expiresAt` | string  | ISO 8601 expiry, when set                    |
| `revokedAt` | string  | ISO 8601 revocation timestamp, once revoked  |

<Callout type="info" title="Note">
  Preview access requires authentication in v1. CrowNest strips its own cookies,
  `Authorization`, and `Referer` before proxying the request to your sandbox app, so
  platform credentials and token-link URLs never reach your code.
</Callout>

## Create a preview [#create-a-preview]

`POST /v1/sandboxes/{sandboxId}/previews` — requires scope
`preview:create`.

Exposes a port on the sandbox. Creating a preview for the same sandbox
and port returns the existing active preview instead of a duplicate. A
sandbox can have at most 3 active previews, and an org at most 10.

| Field      | Type    | Required | Default           | Constraints                |
| ---------- | ------- | -------- | ----------------- | -------------------------- |
| `port`     | integer | Yes      | —                 | 1–65535                    |
| `authMode` | string  | No       | `"authenticated"` | `authenticated` or `token` |

```bash title="Terminal"
curl -X POST https://api.crownest.dev/v1/sandboxes/sbx_abc123/previews \
  -H "Authorization: Bearer $CROWNEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "port": 3000 }'
```

Returns `201` with the preview creation envelope:

```json
{
  "preview": {
    "id": "prv_abc123",
    "slug": "p-a1b2c3",
    "url": "https://p-a1b2c3.crownest.dev",
    "orgId": "org_abc123",
    "projectId": "prj_abc123",
    "sandboxId": "sbx_abc123",
    "port": 3000,
    "authMode": "authenticated",
    "createdAt": "2026-06-11T13:00:00.000Z"
  }
}
```

For a newly created token-mode preview, the response also includes
`previewToken` as a sibling of `preview`. The token is returned only once;
reads, lists, and deduped creates cannot recover it.

```json
{
  "preview": {
    "id": "prv_abc123",
    "slug": "p-a1b2c3",
    "url": "https://p-a1b2c3.crownest.dev",
    "orgId": "org_abc123",
    "projectId": "prj_abc123",
    "sandboxId": "sbx_abc123",
    "port": 3000,
    "authMode": "token",
    "createdAt": "2026-06-11T13:00:00.000Z"
  },
  "previewToken": "pvt_..."
}
```

Errors: `invalid_request`, `unsupported_preview_auth_mode`, `forbidden`,
`not_found`, `sandbox_destroyed`. If the port stops responding later,
requests to the preview URL fail with `preview_unavailable` (502).

## List previews [#list-previews]

`GET /v1/sandboxes/{sandboxId}/previews` — requires scope `preview:read`.

Returns a [paginated](/docs/api/pagination-and-idempotency) list of the
sandbox's previews. This endpoint returns all active previews in a single
page (`hasMore` is always `false`).

```bash title="Terminal"
curl https://api.crownest.dev/v1/sandboxes/sbx_abc123/previews \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

```json
{
  "data": [
    {
      "id": "prv_abc123",
      "url": "https://p-a1b2c3.crownest.dev",
      "port": 3000,
      "authMode": "authenticated"
    }
  ],
  "hasMore": false
}
```

## Get a preview [#get-a-preview]

`GET /v1/previews/{previewId}` — requires scope `preview:read`.

Returns one preview by ID.

```bash title="Terminal"
curl https://api.crownest.dev/v1/previews/prv_abc123 \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

Returns `200` with `{ "preview": { ... } }`. Errors: `forbidden`,
`not_found`.

## Revoke a preview [#revoke-a-preview]

`DELETE /v1/previews/{previewId}` — requires scope `preview:revoke`.

Revokes the preview so its URL stops routing to the sandbox. The
operation is idempotent — revoking an already-revoked preview returns the
revoked record.

```bash title="Terminal"
curl -X DELETE https://api.crownest.dev/v1/previews/prv_abc123 \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

Returns `200` with `{ "preview": { ... } }` including `revokedAt`.
Errors: `forbidden`, `not_found`.

## Next steps [#next-steps]

* Start the server behind your preview with a background request in
  [Commands](/docs/api/commands#run-a-command).
* Check what each scope allows in
  [Authentication](/docs/api/authentication).
