# Start or restore a source-backed preview

Start the configured preview, restoring an archived source workspace when needed. Requires product access. Supply the dashboard frameOrigin. To replace an existing launch, also supply restart.runScriptSha256 for the expected command. A confirmed replacement returns preview.restart with the new launchId, deterministic runId and matching command hash; unconfirmed restarts return 503 preview_unavailable. Session callers also need X-Hoplite-Action: preview-start. The gateway credential expires after five minutes; idempotent replay returns the original credential, so refresh it with GET preview. While the preview boots, relayed requests answer with X-Preview-State: starting (202 for documents, 425 otherwise). This does not publish or validate the application.

`POST /api/platform/v1/apps/{appId}/threads/{threadId}/preview/start`

Required permission: `thread:update`.

## Parameters

| Name | In | Type | Required | Description |

| --- | --- | --- | --- | --- |

| `appId` | path | `string` | Yes | — |

| `threadId` | path | `string` | Yes | — |

| `Idempotency-Key` | header | `string` | No | Required for service credentials. Retry the same request with the same key. |

## Request body

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `restart` | `object` | No | — | — |
| `restart.runScriptSha256` | `string` | Yes | pattern ^[a-f0-9]{64}$ | — |
| `frameOrigin` | `string<uri>` | Yes | max length 2048 | — |

```json
{
  "frameOrigin": "https://dashboard.example.com"
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/platform/v1/apps/appId/threads/threadId/preview/start \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header "Idempotency-Key: $HOPLITE_OPERATION_ID" \
  --header 'Content-Type: application/json' \
  --data '{
  "frameOrigin": "https://dashboard.example.com"
}'
```

## 200 response

Success

Content type: `application/json`. Response type: `object`.

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | true | — |
| `preview` | `object` | Yes | — | — |
| `preview.status` | `string` | Yes | ready · unsupported · reconciling · blocked · starting · stopped · crashed · inactive | — |
| `preview.name` | `string` | No | min length 1 | — |
| `preview.port` | `integer` | No | min 1, max 65535 | — |
| `preview.reason` | `string` | No | — | — |
| `preview.url` | `string<uri>` | No | — | — |
| `preview.restart` | `object` | No | — | — |
| `preview.restart.launchId` | `string` | Yes | min length 1 | — |
| `preview.restart.runId` | `string` | Yes | pattern ^[a-f0-9]{16}$ | — |
| `preview.restart.runScriptSha256` | `string` | Yes | pattern ^[a-f0-9]{64}$ | — |
| `preview.gateway` | `object` | Yes | — | — |
| `preview.gateway.header` | `string` | Yes | x-hoplite-preview-token | — |
| `preview.gateway.token` | `string` | Yes | — | — |
| `preview.gateway.expiresAt` | `string<date-time>` | Yes | — | — |

```json

```

## Status codes

- `200` — Success

- `400` — Invalid request

- `401` — Authentication required

- `403` — Permission, app scope or entitlement denied

- `404` — App, source or thread not found

- `409` — Idempotency conflict or request in progress

- `413` — Request too large

- `503` — Source storage or execution unavailable