# Build a pinned source version

Run an explicit command against a pinned immutable source in an isolated build sandbox using the thread's provider and current resource limits. The live thread workspace is not modified. outputDirectory defaults to dist. Editing/apply admission stays held until the build sandbox is confirmed deleted. A ready build has durable logs, a manifest and a gzip tar artifact. It does not apply source, perform QA or publish an application.

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

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 |
| --- | --- | --- | --- | --- |
| `sourceId` | `string` | Yes | min length 1 | — |
| `command` | `string` | Yes | min length 1, max length 4096 | — |
| `outputDirectory` | `string` | No | — | — |
| `timeoutSeconds` | `integer` | No | min 1, max 600 | — |
| `clientOperationId` | `string` | No | min length 1, max length 128 | — |

```json
{
  "sourceId": "src_saved",
  "command": "pnpm build",
  "outputDirectory": "dist"
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/platform/v1/apps/appId/threads/threadId/builds \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header "Idempotency-Key: $HOPLITE_OPERATION_ID" \
  --header 'Content-Type: application/json' \
  --data '{
  "sourceId": "src_saved",
  "command": "pnpm build",
  "outputDirectory": "dist"
}'
```

## 202 response

Success

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

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | true | — |
| `build` | `object` | Yes | — | — |
| `build.id` | `string` | Yes | — | — |
| `build.projectId` | `string` | Yes | — | — |
| `build.threadId` | `string` | Yes | — | — |
| `build.sourceId` | `string` | Yes | — | — |
| `build.command` | `string` | Yes | — | — |
| `build.outputDirectory` | `string` | Yes | — | — |
| `build.timeoutSeconds` | `number` | Yes | — | — |
| `build.status` | `string` | Yes | queued · building · ready · failed | — |
| `build.error` | `string` | Yes | — | Failure code when status is failed. build_command_failed, build_timeout and build_invalid_output describe the build itself; read the logs. build_infrastructure_unavailable means Hoplite's sandbox infrastructure failed, on every retry or after the command started; creating a new build of the same source can succeed. build_execution_failed is any other unexpected failure. |
| `build.artifactSha256` | `string` | Yes | — | — |
| `build.artifactByteSize` | `number` | Yes | — | — |
| `build.manifestSha256` | `string` | Yes | — | — |
| `build.logSha256` | `string` | Yes | — | — |
| `build.createdAt` | `string` | Yes | — | — |
| `build.startedAt` | `string` | Yes | — | — |
| `build.finishedAt` | `string` | Yes | — | — |

```json

```

## Status codes

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