# Run an agent from a source bundle

Queue an agent run from an immutable source in this app. Requires thread:create and project:read. Uses the existing organization model, billing and tool policies. No GitHub connection is required. A completed run saves an immutable source before updating latestSourceId. Generation is asynchronous; a 201 response acknowledges durable admission.

`POST /api/platform/v1/apps/{appId}/threads`

Required permission: `thread:create`.

## Parameters

| Name | In | Type | Required | Description |

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

| `appId` | 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 |
| --- | --- | --- | --- | --- |
| `sourceId` | `string` | Yes | min length 1, max length 128 | — |
| `prompt` | `string` | Yes | min length 1, max length 100000 | — |
| `title` | `string` | No | max length 200 | — |
| `clientOperationId` | `string` | No | min length 1, max length 128 | — |

```json
{
  "sourceId": "src_saved",
  "prompt": "Create a warm orange landing page"
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/platform/v1/apps/appId/threads \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header "Idempotency-Key: $HOPLITE_OPERATION_ID" \
  --header 'Content-Type: application/json' \
  --data '{
  "sourceId": "src_saved",
  "prompt": "Create a warm orange landing page"
}'
```

## 201 response

Success

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

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | true | — |
| `thread` | `object` | Yes | — | — |
| `thread.id` | `string` | Yes | — | — |
| `thread.projectId` | `string` | Yes | — | — |
| `thread.sourceId` | `string` | Yes | — | — |
| `thread.latestSourceId` | `string` | Yes | — | — |
| `thread.status` | `string` | Yes | queued · running · waiting · blocked · ready · failed · archived | — |
| `thread.latestRunId` | `string` | No | — | — |
| `thread.[key]` | `object` | No | — | — |
| `message` | `object` | No | — | — |
| `message.id` | `string` | Yes | — | — |
| `message.threadId` | `string` | Yes | — | — |
| `message.role` | `string` | Yes | — | — |
| `message.content` | `string` | Yes | — | — |
| `message.[key]` | `object` | No | — | — |
| `run` | `object` | No | — | — |
| `run.id` | `string` | Yes | — | — |
| `run.status` | `string` | Yes | — | — |
| `run.[key]` | `object` | No | — | — |
| `idempotent` | `boolean` | No | — | — |

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