# Create an app

Create an app: the top-level Platform resource that owns sources, threads, previews, drafts and builds. runScript, previewPort, setupScript, defaultModel and instructions configure every thread in the app. externalId is your own identifier, unique per organization when set; metadata holds up to 16 KiB of JSON. idleTimeoutMinutes is the workspace idle default when an activity call omits one. Requires an unrestricted credential with project:create. Retrying with the same Idempotency-Key returns the same app.

`POST /api/platform/v1/apps`

Required permission: `project:create`.

## Parameters

| Name | In | Type | Required | Description |

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

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

## Request body

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | Yes | min length 1, max length 200 | — |
| `externalId` | `string` | No | min length 1, max length 256 | — |
| `metadata` | `object` | No | — | — |
| `metadata.[key]` | `__schema0` | No | — | — |
| `runScript` | `string` | No | max length 10000 | — |
| `setupScript` | `string` | No | max length 10000 | — |
| `previewPort` | `integer` | No | min 3000, max 9999 | — |
| `defaultModel` | `string` | No | min length 1, max length 1024 | — |
| `instructions` | `string` | No | max length 100000 | — |
| `idleTimeoutMinutes` | `integer` | No | min 1, max 60 | — |
| `clientOperationId` | `string` | No | min length 1, max length 128 | — |

```json
{
  "name": "Acme marketing site",
  "externalId": "site_123",
  "metadata": {
    "plan": "pro"
  },
  "runScript": "npm install && npm run dev",
  "previewPort": 3000,
  "defaultModel": "claude-opus-5-5",
  "idleTimeoutMinutes": 20
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/platform/v1/apps \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header "Idempotency-Key: $HOPLITE_OPERATION_ID" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Acme marketing site",
  "externalId": "site_123",
  "metadata": {
    "plan": "pro"
  },
  "runScript": "npm install && npm run dev",
  "previewPort": 3000,
  "defaultModel": "claude-opus-5-5",
  "idleTimeoutMinutes": 20
}'
```

## 201 response

Success

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

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | true | — |
| `app` | `object` | Yes | — | — |
| `app.id` | `string` | Yes | — | — |
| `app.name` | `string` | Yes | — | — |
| `app.externalId` | `string` | Yes | — | — |
| `app.metadata` | `object` | Yes | — | — |
| `app.metadata.[key]` | `__schema0` | No | — | — |
| `app.runScript` | `string` | Yes | — | — |
| `app.setupScript` | `string` | Yes | — | — |
| `app.previewPort` | `integer` | Yes | min -9007199254740991, max 9007199254740991 | — |
| `app.defaultModel` | `string` | Yes | — | — |
| `app.instructions` | `string` | Yes | — | — |
| `app.idleTimeoutMinutes` | `integer` | Yes | min -9007199254740991, max 9007199254740991 | — |
| `app.createdAt` | `string<date-time>` | Yes | — | — |
| `app.updatedAt` | `string<date-time>` | Yes | — | — |
| `idempotent` | `boolean` | Yes | — | — |

```json

```

## Status codes

- `201` — 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