# Create a thread

Start a coding-agent thread in a project. A prompt or at least one previously presigned attachment ticket is required. An optional autoMerge value creates a durable thread override; omitted, the thread keeps inheriting the organization's live default. Automatic PR fixes are always inherited from the thread's project. Reusing clientOperationId makes retries idempotent.

`POST /api/threads`

Required permission: `thread:create`.

## Parameters

| Name | In | Type | Required | Description |

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

| `Idempotency-Key` | header | `string` | No | Required for writes authenticated by a service credential and for service-account lifecycle writes. Other session writes may opt in. Reuse the key and payload for retries. Settled keys remain reserved for at least seven days; a transient 4xx releases its key. Changed payloads return 409. Match any legacy clientOperationId/clientMessageId in the body. |

## Request body

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `Option 1` | `object` | No | — | — |
| `Option 1.prompt` | `string` | No | pattern \S | — |
| `Option 2` | `object` | No | — | — |
| `Option 2.initialAttachmentTickets` | `unknown[]` | Yes | min 1 items | — |

```json
{
  "projectId": "proj_01JQ75",
  "prompt": "Add analytics to the checkout confirmation flow.",
  "title": "Add checkout analytics",
  "repos": [
    {
      "repoFullName": "acme/storefront",
      "branch": "main"
    }
  ],
  "speed": "fast",
  "autoMerge": true,
  "clientOperationId": "checkout-analytics-2026-08-10"
}
```

## Example request

```bash
curl --request POST \
  --url https://api.hoplite.sh/api/threads \
  --header "X-Api-Key: $HOPLITE_API_KEY" \
  --header 'Idempotency-Key: checkout-analytics-2026-08-10' \
  --header 'Content-Type: application/json' \
  --data '{
  "projectId": "proj_01JQ75",
  "prompt": "Add analytics to the checkout confirmation flow.",
  "title": "Add checkout analytics",
  "repos": [
    {
      "repoFullName": "acme/storefront",
      "branch": "main"
    }
  ],
  "speed": "fast",
  "autoMerge": true,
  "clientOperationId": "checkout-analytics-2026-08-10"
}'
```

## 201 response

The thread was created and queued.

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

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | constant true | — |
| `thread` | `Thread` | Yes | — | — |
| `thread.id` | `string` | Yes | — | — |
| `thread.projectId` | `string` | Yes | — | — |
| `thread.modelId` | `string \| null` | No | — | — |
| `thread.githubPullRequestNumber` | `integer \| null` | No | min 1 | — |
| `thread.title` | `string \| null` | No | — | — |
| `thread.status` | `ThreadStatus` | Yes | queued · running · waiting · blocked · ready · failed · archived | — |
| `thread.archivedAt` | `string \| null` | No | — | — |
| `thread.initializationState` | `string` | No | accepted · preparing · completed · failed · cancelled | — |
| `thread.initializationPhase` | `string \| null` | No | provisioning_workspace · resolving_repository · allocating_sandbox · fetching_repository · configuring_workspace · dispatching_run ·  | — |
| `thread.initializationAttemptCount` | `integer` | No | min 0 | — |
| `thread.initializationErrorCode` | `string \| null` | No | — | — |
| `thread.initializationErrorMessage` | `string \| null` | No | — | — |
| `thread.initializationUpdatedAt` | `string \| null` | No | — | — |
| `thread.waitingOn` | `string[]` | Yes | — | — |
| `thread.waitingOn.[item]` | `string` | No | — | — |
| `thread.blockedOn` | `string[]` | Yes | — | — |
| `thread.blockedOn.[item]` | `string` | No | — | — |
| `thread.pendingWakeups` | `integer` | Yes | min 0 | — |
| `thread.tasks` | `ThreadTask[]` | Yes | — | — |
| `thread.tasks.id` | `string` | Yes | — | — |
| `thread.tasks.identifier` | `string` | No | — | — |
| `thread.tasks.title` | `string` | No | — | — |
| `thread.tasks.status` | `string` | No | — | — |
| `thread.tasks.threadIndex` | `number \| null` | No | — | — |
| `thread.pullRequests` | `PullRequest[]` | Yes | — | — |
| `thread.pullRequests.id` | `string` | No | — | — |
| `thread.pullRequests.threadId` | `string` | No | — | — |
| `thread.pullRequests.number` | `number` | Yes | — | — |
| `thread.pullRequests.url` | `string<uri>` | Yes | — | — |
| `thread.pullRequests.repoFullName` | `string` | Yes | — | — |
| `thread.pullRequests.state` | `string` | Yes | — | — |
| `thread.pullRequests.headRef` | `string` | No | — | — |
| `thread.pullRequests.headSha` | `string` | No | — | — |
| `thread.pullRequests.baseRef` | `string` | No | — | — |
| `thread.pullRequests.draft` | `boolean` | No | — | — |
| `thread.pullRequests.version` | `integer` | No | min 0 | — |
| `thread.pullRequests.githubSummary` | `PullRequestSummary \| null` | No | — | — |
| `thread.pullRequests.githubSummary.Option 1` | `PullRequestSummary` | No | — | — |
| `thread.pullRequests.githubSummary.Option 1.state` | `string` | Yes | — | — |
| `thread.pullRequests.githubSummary.Option 1.draft` | `boolean` | Yes | — | — |
| `thread.pullRequests.githubSummary.Option 1.mergeable` | `string` | Yes | mergeable · conflicting · unknown | — |
| `thread.pullRequests.githubSummary.Option 1.mergeQueued` | `boolean` | No | — | — |
| `thread.pullRequests.githubSummary.Option 1.checks` | `PullRequestChecksSummary` | Yes | — | — |
| `thread.pullRequests.githubSummary.Option 1.checks.total` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.githubSummary.Option 1.checks.succeeded` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.githubSummary.Option 1.checks.failing` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.githubSummary.Option 1.checks.running` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.githubSummary.Option 1.checksAvailable` | `boolean` | Yes | — | — |
| `thread.pullRequests.githubSummary.Option 1.commentsAvailable` | `boolean` | Yes | — | — |
| `thread.pullRequests.githubSummary.Option 1.unresolvedCount` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.githubSummary.Option 2` | `null` | No | — | — |
| `thread.pullRequests.githubSummaryUpdatedAt` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop` | `PullRequestReviewLoop \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1` | `PullRequestReviewLoop` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.id` | `string` | Yes | — | — |
| `thread.pullRequests.reviewLoop.Option 1.threadId` | `string` | Yes | — | — |
| `thread.pullRequests.reviewLoop.Option 1.pullRequestId` | `string` | Yes | — | — |
| `thread.pullRequests.reviewLoop.Option 1.enabled` | `boolean` | Yes | — | — |
| `thread.pullRequests.reviewLoop.Option 1.autoMerge` | `boolean` | Yes | — | — |
| `thread.pullRequests.reviewLoop.Option 1.disabledBy` | `string \| null` | No | user · system ·  | — |
| `thread.pullRequests.reviewLoop.Option 1.status` | `string` | Yes | disabled · observing · waiting_checks · fixing · finalizing · ready · blocked · error | — |
| `thread.pullRequests.reviewLoop.Option 1.headSha` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.lastProcessedHeadSha` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.lastObservedAt` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.waitUntil` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.lastFixRunId` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.passCount` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.reviewLoop.Option 1.operationalRetryCount` | `integer` | Yes | min 0 | — |
| `thread.pullRequests.reviewLoop.Option 1.lastErrorCode` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.lastErrorMessage` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.blockedReason` | `string \| null` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.createdAt` | `string<date-time>` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 1.updatedAt` | `string<date-time>` | No | — | — |
| `thread.pullRequests.reviewLoop.Option 2` | `null` | No | — | — |
| `thread.taskId` | `string` | No | — | — |
| `thread.taskIdentifier` | `string \| null` | No | — | — |
| `thread.linearIssueUrl` | `string \| null` | No | — | — |
| `thread.primaryPullRequest` | `PullRequest \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1` | `PullRequest` | No | — | — |
| `thread.primaryPullRequest.Option 1.id` | `string` | No | — | — |
| `thread.primaryPullRequest.Option 1.threadId` | `string` | No | — | — |
| `thread.primaryPullRequest.Option 1.number` | `number` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.url` | `string<uri>` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.repoFullName` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.state` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.headRef` | `string` | No | — | — |
| `thread.primaryPullRequest.Option 1.headSha` | `string` | No | — | — |
| `thread.primaryPullRequest.Option 1.baseRef` | `string` | No | — | — |
| `thread.primaryPullRequest.Option 1.draft` | `boolean` | No | — | — |
| `thread.primaryPullRequest.Option 1.version` | `integer` | No | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary` | `PullRequestSummary \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1` | `PullRequestSummary` | No | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.state` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.draft` | `boolean` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.mergeable` | `string` | Yes | mergeable · conflicting · unknown | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.mergeQueued` | `boolean` | No | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checks` | `PullRequestChecksSummary` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checks.total` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checks.succeeded` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checks.failing` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checks.running` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.checksAvailable` | `boolean` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.commentsAvailable` | `boolean` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 1.unresolvedCount` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.githubSummary.Option 2` | `null` | No | — | — |
| `thread.primaryPullRequest.Option 1.githubSummaryUpdatedAt` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop` | `PullRequestReviewLoop \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1` | `PullRequestReviewLoop` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.id` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.threadId` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.pullRequestId` | `string` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.enabled` | `boolean` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.autoMerge` | `boolean` | Yes | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.disabledBy` | `string \| null` | No | user · system ·  | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.status` | `string` | Yes | disabled · observing · waiting_checks · fixing · finalizing · ready · blocked · error | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.headSha` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.lastProcessedHeadSha` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.lastObservedAt` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.waitUntil` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.lastFixRunId` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.passCount` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.operationalRetryCount` | `integer` | Yes | min 0 | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.lastErrorCode` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.lastErrorMessage` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.blockedReason` | `string \| null` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.createdAt` | `string<date-time>` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 1.updatedAt` | `string<date-time>` | No | — | — |
| `thread.primaryPullRequest.Option 1.reviewLoop.Option 2` | `null` | No | — | — |
| `thread.primaryPullRequest.Option 2` | `null` | No | — | — |
| `thread.createdAt` | `string<date-time>` | No | — | — |
| `thread.updatedAt` | `string<date-time>` | No | — | — |
| `thread.spendLimitMicros` | `integer \| null` | No | min 1 | — |
| `thread.version` | `integer` | No | min 0 | — |
| `thread.titleVersion` | `integer` | No | min 0 | — |
| `thread.archiveVersion` | `integer` | No | min 0 | — |
| `thread.latestUserMessageAt` | `string<date-time>` | No | — | — |
| `thread.workspaceStatus` | `string \| null` | No | preparing · cancelled · ready · archived · cleanup_pending · error ·  | — |
| `thread.workspaceSetupStatus` | `string \| null` | No | pending · running · ready · failed ·  | — |
| `thread.latestRunId` | `string \| null` | No | — | — |
| `thread.sourceId` | `string \| null` | No | — | Initial immutable Platform source, when source-backed. |
| `thread.latestSourceId` | `string \| null` | No | — | Latest successful immutable Platform source. |
| `thread.runStartedAt` | `string \| null` | No | — | — |
| `thread.runTurnStartedAt` | `string \| null` | No | — | Durable anchor for the admitted turn; unlike runStartedAt it spans compaction and follow-up runs. |
| `thread.runStatus` | `string \| null` | No | queued · running · waiting · completed · failed · cancelled ·  | Status of the latest run; distinguishes a genuinely queued run from a previous terminal run during the optimistic new-run window. |
| `thread.runSteerable` | `boolean \| null` | No | — | Whether the active run accepts same-run steering; false while a compact, repair, or foreign-workflow run holds the thread, so a follow-up would queue behind it. Null with no active run. |
| `thread.stopRequestedAt` | `string \| null` | No | — | — |
| `thread.runActiveDurationMs` | `integer \| null` | No | min 0 | — |
| `thread.runActiveDurationProvenance` | `string` | No | accounted · legacy_default · unknown | Whether the active duration was actively accounted, inherited from the legacy database default, or cannot be attributed during rollout. |
| `thread.runActiveSegmentStartedAt` | `string \| null` | No | — | — |
| `thread.sentryIssue` | `SentryIssueLink \| null` | No | — | — |
| `thread.sentryIssue.Option 1` | `SentryIssueLink` | No | — | — |
| `thread.sentryIssue.Option 1.issueId` | `string` | Yes | — | — |
| `thread.sentryIssue.Option 1.shortId` | `string \| null` | No | — | — |
| `thread.sentryIssue.Option 1.title` | `string \| null` | No | — | — |
| `thread.sentryIssue.Option 1.level` | `string \| null` | No | — | — |
| `thread.sentryIssue.Option 1.projectId` | `string` | Yes | — | — |
| `thread.sentryIssue.Option 1.projectSlug` | `string` | Yes | — | — |
| `thread.sentryIssue.Option 1.organizationSlug` | `string` | Yes | — | — |
| `thread.sentryIssue.Option 1.url` | `string<uri>` | Yes | — | — |
| `thread.sentryIssue.Option 2` | `null` | No | — | — |
| `thread.createdByUserId` | `string \| null` | No | — | — |
| `thread.createdByAutomation` | `boolean` | No | — | — |
| `thread.participantUserIds` | `string[]` | No | — | — |
| `thread.participantUserIds.[item]` | `string` | No | — | — |
| `thread.executionTarget` | `LocalAgentExecutionTarget \| null` | No | — | — |
| `thread.executionTarget.Option 1` | `LocalAgentExecutionTarget` | No | — | — |
| `thread.executionTarget.Option 1.bindingId` | `string` | Yes | min length 1, max length 128 | — |
| `thread.executionTarget.Option 1.hostId` | `string` | Yes | min length 1, max length 128 | — |
| `thread.executionTarget.Option 1.kind` | `string` | Yes | constant "local" | — |
| `thread.executionTarget.Option 1.model` | `string \| null` | No | min length 1, max length 128 | — |
| `thread.executionTarget.Option 1.provider` | `string` | Yes | codex · claude | — |
| `thread.executionTarget.Option 1.workspaceMode` | `string` | Yes | direct · worktree | — |
| `thread.executionTarget.Option 2` | `null` | No | — | — |
| `idempotent` | `boolean` | No | — | — |
| `message` | `Message \| null` | No | — | — |
| `message.Option 1` | `Message` | No | — | — |
| `message.Option 1.id` | `string` | Yes | — | — |
| `message.Option 1.threadId` | `string` | Yes | — | — |
| `message.Option 1.runId` | `string \| null` | No | — | — |
| `message.Option 1.queuedAfterRunId` | `string \| null` | No | — | — |
| `message.Option 1.authorUserId` | `string \| null` | No | — | — |
| `message.Option 1.author` | `UserSummary \| null` | No | — | — |
| `message.Option 1.author.Option 1` | `UserSummary` | No | — | — |
| `message.Option 1.author.Option 1.id` | `string` | Yes | — | — |
| `message.Option 1.author.Option 1.name` | `string` | Yes | — | — |
| `message.Option 1.author.Option 1.image` | `string \| null` | No | — | — |
| `message.Option 1.author.Option 2` | `null` | No | — | — |
| `message.Option 1.role` | `string` | Yes | user · assistant · system · tool | — |
| `message.Option 1.kind` | `string` | Yes | chat · thinking · tool · status | — |
| `message.Option 1.content` | `string` | Yes | — | — |
| `message.Option 1.createdAt` | `string<date-time>` | Yes | — | — |
| `message.Option 1.toolCallId` | `string` | No | — | — |
| `message.Option 1.toolName` | `string` | No | — | — |
| `message.Option 1.checkpointId` | `string` | No | — | — |
| `message.Option 1.metadata` | `object \| null` | No | — | — |
| `message.Option 1.metadata.[key]` | `object` | No | — | An additional property with any JSON value. |
| `message.Option 2` | `null` | No | — | — |
| `run` | `RunSummary \| null` | No | — | — |
| `run.Option 1` | `RunSummary` | No | — | — |
| `run.Option 1.id` | `string` | Yes | — | — |
| `run.Option 1.status` | `string` | No | — | — |
| `run.Option 2` | `null` | No | — | — |

```json
{
  "ok": true,
  "thread": {
    "id": "thr_01JQ7B",
    "projectId": "proj_01JQ75",
    "title": "Add checkout analytics",
    "status": "queued",
    "waitingOn": [],
    "blockedOn": [],
    "pendingWakeups": 0,
    "tasks": [],
    "pullRequests": []
  },
  "run": {
    "id": "run_01JQ7C",
    "status": "queued"
  }
}
```

## Response headers

| Header | Type | Statuses | Description |

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

| `RateLimit` | `string` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Current ingress policy, remaining requests, and seconds until reset, for example `"ingress";r=599;t=60`. |

| `RateLimit-Policy` | `string` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Ingress quota and window, for example `"ingress";q=600;qu="requests";w=60`. |

| `X-RateLimit-Limit` | `integer` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Maximum requests allowed in the current rolling window. |

| `X-RateLimit-Remaining` | `integer` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Requests remaining in the current rolling window. |

| `X-RateLimit-Reset` | `integer` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Unix timestamp when the current rolling window resets. |

| `x-request-id` | `string` | `201`, `400`, `401`, `402`, `403`, `409`, `413`, `429`, `500`, `501`, `503` | Request correlation ID. |

| `Location` | `string` | `201` | Canonical path of the created thread. |

| `Retry-After` | `integer` | `429`, `503` | Seconds until another request should be attempted. |

| `X-Retry-After` | `integer` | `429`, `503` | Legacy retry delay in seconds. |

## Status codes

- `201` — The thread was created and queued.

- `400` — The thread payload, model, metadata, or attachment ticket is invalid.

- `401` — The API key is missing, invalid, or lacks the operation's permission.

- `402` — The organization does not have access to the requested product capability.

- `403` — The key's current user no longer has the required organization membership or role.

- `409` — The idempotency key conflicts with an existing operation or workspace state (client_operation_conflict), or the requested local execution target is no longer available. Local-target errors are local_agent_target_unavailable, local_agent_host_offline, and local_agent_provider_unavailable.

- `413` — The request or attachment payload is too large.

- `429` — The API-key rate limit has been exceeded.

- `500` — The API encountered an unexpected failure.

- `501` — The deployment has not configured the requested capability.

- `503` — A required API dependency, including the rate limiter, is unavailable.