# Queue a draft for integration into the app

Requires project:update and thread:update. Pin expectedSourceId to a completed run's latest source. The durable per-app queue performs a three-way merge against the current head. Automatic agent reconciliation is the default: conflicts start billable repair runs on the same thread while the apply remains queued, within a 15-minute total repair budget. Validation failures are returned to the agent; three identical invalid candidates stop the loop. Set conflictResolution to manual to return conflicts without agent work. Repairs expose run IDs and reports. Normal edits are blocked while queued; applied drafts are sealed. Unresolved conflicts leave the head unchanged and reopen the draft. To choose head or draft files for conflicts, provide resolutions and pin expectedHeadSourceId. Every retry must reuse clientOperationId. An applied draft is sealed; start a new thread from the app head to continue. Validation covers source integrity, paths, limits and merge conflicts; no application build or browser QA is implied. Applying never publishes.

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

Required permission: `project: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 |
| --- | --- | --- | --- | --- |
| `expectedSourceId` | `string` | Yes | min length 1, max length 128 | — |
| `conflictResolution` | `string` | No | agent · manual | — |
| `clientOperationId` | `string` | Yes | min length 1, max length 128 | — |
| `expectedHeadSourceId` | `string` | No | min length 1, max length 128 | — |
| `resolutions` | `object[]` | No | max 1000 items | — |
| `resolutions.path` | `string` | Yes | min length 1, max length 1024 | — |
| `resolutions.take` | `string` | Yes | head · draft | — |

```json
{
  "expectedSourceId": "src_edited",
  "clientOperationId": "apply-draft-1"
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/platform/v1/apps/appId/threads/threadId/apply \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header 'Idempotency-Key: apply-draft-1' \
  --header 'Content-Type: application/json' \
  --data '{
  "expectedSourceId": "src_edited",
  "clientOperationId": "apply-draft-1"
}'
```

## 202 response

Success

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

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | true | — |
| `apply` | `object` | Yes | — | — |
| `apply.id` | `string` | Yes | — | — |
| `apply.sequence` | `integer` | Yes | min -9007199254740991, max 9007199254740991 | — |
| `apply.conflictResolution` | `string` | Yes | agent · manual | — |
| `apply.repairs` | `object[]` | Yes | — | — |
| `apply.repairs.runId` | `string` | Yes | — | — |
| `apply.repairs.headSourceId` | `string` | Yes | — | — |
| `apply.repairs.revision` | `integer` | Yes | min -9007199254740991, max 9007199254740991 | — |
| `apply.repairs.startedAt` | `string` | Yes | — | — |
| `apply.repairs.error` | `string` | Yes | — | — |
| `apply.repairs.report` | `object` | Yes | — | — |
| `apply.repairs.report.status` | `string` | Yes | resolved · unresolved | — |
| `apply.repairs.report.summary` | `string` | Yes | min length 1, max length 4000 | — |
| `apply.repairs.report.checks` | `string[]` | Yes | max 30 items | — |
| `apply.repairs.report.checks.[item]` | `string` | No | max length 1000 | — |
| `apply.repairs.candidateFingerprint` | `string` | No | — | — |
| `apply.orgId` | `string` | Yes | — | — |
| `apply.projectId` | `string` | Yes | — | — |
| `apply.threadId` | `string` | Yes | — | — |
| `apply.baseSourceId` | `string` | Yes | — | — |
| `apply.draftSourceId` | `string` | Yes | — | — |
| `apply.headSourceId` | `string` | Yes | — | — |
| `apply.resultSourceId` | `string` | Yes | — | — |
| `apply.expectedHeadSourceId` | `string` | Yes | — | — |
| `apply.resolutions` | `object[]` | Yes | — | — |
| `apply.resolutions.path` | `string` | Yes | — | — |
| `apply.resolutions.take` | `string` | Yes | head · draft | — |
| `apply.status` | `string` | Yes | queued · applied · conflicted · failed | — |
| `apply.conflicts` | `string[]` | Yes | — | — |
| `apply.conflicts.[item]` | `string` | No | — | — |
| `apply.error` | `string` | Yes | — | — |
| `apply.createdByUserId` | `string` | Yes | — | — |
| `apply.createdAt` | `string` | Yes | — | — |
| `apply.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