---
title: "Quickstart"
description: "Create an app, run an agent edit, preview it, and build the result with the Platform API."
canonical_url: "https://hoplite.sh/docs/platform/quickstart"
markdown_url: "https://hoplite.sh/docs/platform/quickstart.md"
---

# Quickstart
URL: /docs/platform/quickstart
LLM index: /llms.txt
Description: Create an app, run an agent edit, preview it, and build the result with the Platform API.

# Platform quickstart

This walks one customer site through the Platform API: create an app, import its files, run an agent edit, preview the result, and build a downloadable artifact. Run every request from your backend. Each step reuses IDs from the previous one.

```sh
export HOPLITE_API_URL="https://api.hoplite.sh"
export HOPLITE_SERVICE_KEY="hop_svc_..."   # step 2
```

## 1. Get access

The Platform API is enabled per organization. Until Hoplite enables yours, every `/api/platform/v1` request answers:

```json
{ "ok": false, "error": "platform_api_not_enabled" }
```

with status `403` and no side effects. Contact Hoplite to enable your organization. Agent runs, workspaces, and builds use your workspace's normal billing, so the workspace needs an active plan.

## 2. Create a service credential

As an owner or admin, [create a service account](/docs/api/authentication#create-a-service-account) under **Settings → Workspace → API keys → Service accounts** with `project:create`, `project:read`, `project:update`, `thread:create`, `thread:read`, and `thread:update`. Leave **Project IDs** blank so it can create apps. Keep the key on your server; never send it to a browser.

Service-credential writes require an `Idempotency-Key` header. Reuse the same key when retrying the same request.

## 3. Create an app

An app is one customer site or workspace in your product. Its `runScript` and `previewPort` configure previews for every thread in the app. `externalId` is your own identifier.

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: app-site-123' \
  --data '{"name":"Acme site","externalId":"site_123","runScript":"npx --yes serve -l 3000 .","previewPort":3000,"defaultModel":"claude-opus-5-5"}'
```

```json
{ "ok": true, "idempotent": false, "app": { "id": "app_…", "name": "Acme site", "externalId": "site_123", "previewPort": 3000, … } }
```

```sh
export APP_ID="app_…"
```

## 4. Import a source and initialize the head

A source is an immutable bundle of files. The head is the app's accepted version that drafts are applied to.

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/sources" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: starter-1' \
  --data '{"files":[{"path":"index.html","content":"<h1>Hello</h1>"}]}'
```

```json
{ "ok": true, "source": { "id": "src_…", "projectId": "app_…", "fileCount": 1, … } }
```

```sh
export SOURCE_ID="src_…"
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/source-head/initialize" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: head-1' \
  --data "{\"sourceId\":\"$SOURCE_ID\"}"
```

```json
{ "ok": true, "head": { "sourceId": "src_…", "revision": 0, … } }
```

## 5. Start a thread and wait for the run

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/threads" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: edit-1' \
  --data "{\"sourceId\":\"$SOURCE_ID\",\"prompt\":\"Create a warm orange landing page\"}"
```

```json
{ "ok": true, "thread": { "id": "thr_…", "sourceId": "src_…", … }, "run": { "id": "run_…", "status": "queued" } }
```

Poll the run until it leaves `queued`, `running`, and `waiting`:

```sh
export THREAD_ID="thr_…" RUN_ID="run_…"
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/threads/$THREAD_ID/runs/$RUN_ID" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY"
```

```json
{ "ok": true, "run": { "id": "run_…", "status": "completed", "resultSourceId": "src_…", … } }
```

Read the agent's replies with `GET /api/platform/v1/apps/$APP_ID/threads/$THREAD_ID/messages`. Send a follow-up with `POST` to the same path and `{"content":"Add a contact page"}`.

## 6. Read the result files

```sh
export RESULT_ID="src_…"   # run.resultSourceId
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/sources/$RESULT_ID/files" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY"
```

```json
{ "ok": true, "source": { "id": "src_…", … }, "files": [{ "path": "index.html", "encoding": "base64", "content": "PCFkb2N0eXBl…" }] }
```

File contents are always base64, including text.

## 7. Start a preview

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/threads/$THREAD_ID/preview/start" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: preview-1' \
  --data '{"frameOrigin":"https://dashboard.example.com"}'
```

```json
{ "ok": true, "preview": { "status": "starting", "gateway": null } }
```

Poll `GET .../preview?frameOrigin=https%3A%2F%2Fdashboard.example.com` until `status` is `ready`. The response then carries a `url` and a five-minute `gateway` credential. Relay it through your own authenticated gateway as described in [Embedded previews](/docs/platform/previews); never give the credential to a browser.

## 8. Apply the draft and build it

Apply the run's result to the app head. `clientOperationId` must match the `Idempotency-Key`.

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/threads/$THREAD_ID/apply" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: apply-1' \
  --data "{\"expectedSourceId\":\"$RESULT_ID\",\"clientOperationId\":\"apply-1\"}"
```

```json
{ "ok": true, "apply": { "id": "apply_…", "status": "queued", … } }
```

Poll `GET /api/platform/v1/apps/$APP_ID/applies/{applyId}` until `status` is `applied`. Then build the result:

```sh
curl "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/threads/$THREAD_ID/builds" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: build-1' \
  --data "{\"sourceId\":\"$RESULT_ID\",\"command\":\"mkdir -p dist && cp index.html dist/\"}"
```

```json
{ "ok": true, "build": { "id": "build_…", "status": "queued", … } }
```

Poll `GET /api/platform/v1/apps/$APP_ID/builds/{buildId}` until `status` is `ready`, then download the gzip tar archive:

```sh
export BUILD_ID="build_…"
curl -fo build.tar.gz "$HOPLITE_API_URL/api/platform/v1/apps/$APP_ID/builds/$BUILD_ID/artifact" \
  -H "Authorization: Bearer $HOPLITE_SERVICE_KEY"
```

Check it against `/manifest` before deploying it to your own hosting. Hoplite never publishes it for you.

## 9. Next steps

- Stream progress with [public events](/docs/api/streamPublicEvents) or [signed webhooks](/docs/api/createWebhook) instead of polling.
- [Versions and builds](/docs/platform/versions) covers concurrent drafts, conflicts, and build limits.
- [Lifecycle and reliability](/docs/platform/operations) covers idle workspaces, retries, and source retention. Set an app-wide idle default with `idleTimeoutMinutes` on the app.
- Put app IDs in a service account's **Project IDs** to restrict it to those apps; a restricted credential lists only those apps and cannot create new ones.

## Sitemap

See the full [sitemap](/docs/sitemap.md) for all pages.
Well-known sitemap: [/docs/.well-known/sitemap.md](/docs/.well-known/sitemap.md).
