> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orgo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Threads

> Stored transcripts for chat completions

A thread is a stored transcript attached to one computer. Threads are what the dashboard reads to show a conversation, and what [Create chat completion](/api-reference/chat/completions) writes each turn into.

<Warning>
  A thread is a record, not a memory. Passing a `thread_id` to `POST /v1/chat/completions` does **not** replay the thread's messages to the model. Each completion **replaces** the thread's stored messages with that request's transcript rather than appending to it. Keep the conversation client-side and resend it in full on every completion. See [Threads and history](/api-reference/chat/completions#threads-and-history).
</Warning>

There are two thread surfaces, and they are not interchangeable.

| Surface | Use it for | Identifier it accepts | Error shape |
| - | - | - | - |
| `/v1/threads` | Programmatic access alongside `/v1/chat/completions`. | Computer UUID or instance id. | `{ "error": { "type", "message", "code" } }` |
| `/chat/threads` | The surface the Orgo dashboard uses. Returns assistant-ui-shaped fields. | Computer UUID only. | `{ "error": "message" }` |

**Base URL:** `https://www.orgo.ai/api`
**Auth:** `Authorization: Bearer $ORGO_API_KEY` on every request. Both surfaces require an account-wide key. A workspace-scoped key is refused on every thread endpoint.

***

## The `/v1` surface

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/v1/threads?computer_id={id}` | List threads on a computer. |
| `POST` | `/v1/threads` | Create an empty thread. |
| `GET` | `/v1/threads/{thread_id}` | Get a thread with its messages. |
| `DELETE` | `/v1/threads/{thread_id}` | Delete a thread. |

### List threads

```http theme={null}
GET /v1/threads?computer_id={id}
```

Returns every thread stored against the computer. Threads are scoped to the computer's workspace, not to whoever created them, so every workspace member sees the same list. Message bodies are not included, only a count.

<ParamField query="computer_id" type="string" required>
  The computer's UUID or its instance id. Omitting it returns 400 `missing_computer_id`.
</ParamField>

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "object": "thread",
      "created_at": "2026-04-20T14:22:05.123Z",
      "title": "GitHub search",
      "message_count": 4
    }
  ]
}
```

<ResponseField name="object" type="string">Always `list`.</ResponseField>

<ResponseField name="data" type="array">
  Thread summaries, most recently updated first.

  <Expandable title="thread">
    <ResponseField name="id" type="string">Thread UUID.</ResponseField>
    <ResponseField name="object" type="string">Always `thread`.</ResponseField>
    <ResponseField name="created_at" type="string">ISO 8601 creation timestamp.</ResponseField>
    <ResponseField name="title" type="string">Title, or `null` if none has been generated.</ResponseField>
    <ResponseField name="message_count" type="integer">Number of stored messages.</ResponseField>
  </Expandable>
</ResponseField>

```bash theme={null}
curl "https://www.orgo.ai/api/v1/threads?computer_id=$COMPUTER_ID" \
  -H "Authorization: Bearer $ORGO_API_KEY"
```

### Create thread

```http theme={null}
POST /v1/threads
```

Creates an empty thread bound to a computer, and returns `201 Created`. You rarely need this. A completion with no `thread_id` creates one for you.

<ParamField body="computer_id" type="string" required>
  The computer's UUID or its instance id. Omitting it returns 400 `missing_computer_id`.
</ParamField>

```json theme={null}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "thread",
  "created_at": "2026-04-20T14:22:05.123Z"
}
```

```bash theme={null}
curl -X POST https://www.orgo.ai/api/v1/threads \
  -H "Authorization: Bearer $ORGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"computer_id": "'"$COMPUTER_ID"'"}'
```

### Get thread

```http theme={null}
GET /v1/threads/{thread_id}
```

Returns the thread with its full stored transcript. On this surface a thread is readable only by the account that created it. A workspace teammate gets 404, not 403. A thread id that is not a UUID returns 500.

```json theme={null}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "thread",
  "created_at": "2026-04-20T14:22:05.123Z",
  "title": "GitHub search",
  "messages": [
    { "role": "user", "content": "Open github.com" },
    { "role": "assistant", "content": "Done. GitHub is open." }
  ]
}
```

### Delete thread

```http theme={null}
DELETE /v1/threads/{thread_id}
```

Permanently deletes the thread and its messages. Creator only, like `GET`. The creator also needs write access to the computer's workspace. A view-only member gets 401 even for a thread they created.

```json theme={null}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "thread.deleted",
  "deleted": true
}
```

### Errors

| Status | Code | When |
| - | - | - |
| 400 | `missing_computer_id` | `computer_id` was not supplied on list or create. |
| 401 | `invalid_api_key` | Every authentication or access failure: a missing or unknown key, a workspace-scoped key, a computer or thread in a workspace you cannot access, or view-only access on `POST` and `DELETE`. This surface returns one body for all of them, with `type` `authentication_error` and the message `Invalid API key.` |
| 403 | `forbidden` | On list or create: no computer matches `computer_id`, or it can no longer run (a free trial computer that has expired, or a computer paid for by its own subscription or dedicated purchase whose payment has lapsed). |
| 403 | `model_proxy_disabled` | The OpenAI-compatible endpoint is switched off for this account, so list, create, and get are refused. `DELETE` is deliberately exempt, so you can always remove what is held. |
| 404 | `thread_not_found` | The thread does not exist, or you are not the account that created it. |
| 500 | None | Unexpected server error. Also returned for a malformed JSON body on `POST` and for a thread id that is not a UUID. Neither is caught, so neither carries the `error` envelope below. |

```json theme={null}
{
  "error": {
    "type": "not_found",
    "message": "Thread not found.",
    "code": "thread_not_found"
  }
}
```

`type` is one of `invalid_request`, `authentication_error`, `permission_error`, or `not_found`.

***

## The `/chat` surface

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/chat/threads?desktopId={uuid}` | List threads with full message history. |
| `POST` | `/chat/threads` | Create an empty thread. |
| `GET` | `/chat/threads/{thread_id}` | Get a thread with full message history. |
| `PATCH` | `/chat/threads/{thread_id}` | Update title, replace messages, archive, or unarchive. |
| `DELETE` | `/chat/threads/{thread_id}` | Delete a thread. |
| `POST` | `/chat/threads/{thread_id}/title` | Generate a title from the first messages. |
| `GET` | `/chat/threads/{thread_id}/run` | Whether a dashboard chat run is in progress on this thread. |
| `DELETE` | `/chat/threads/{thread_id}/run` | Stop that run. |

Access on this surface is by workspace, not by author. Any member of the computer's workspace can read its threads. Creating, updating, deleting, and generating a title need write access to the workspace, so a view-only member gets 403. The `/run` endpoints are the exception and are restricted to the thread's creator.

### List threads

```http theme={null}
GET /chat/threads?desktopId={uuid}
```

Returns every thread on the given computer that you can see, including full message history. Archived threads are returned too; filter on `status` client-side if you want to hide them.

<ParamField query="desktopId" type="string" required>
  The computer's UUID. This surface does not accept an instance id: a non-UUID value returns 500. Omitting it returns 400.
</ParamField>

<ResponseField name="threads" type="array">
  Thread objects, most recently updated first.

  <Expandable title="thread">
    <ResponseField name="id" type="string">Thread UUID.</ResponseField>
    <ResponseField name="remoteId" type="string">Same as `id`. Kept for compatibility with assistant-ui clients.</ResponseField>
    <ResponseField name="status" type="string">`regular` or `archived`. A newly created thread is `regular`.</ResponseField>
    <ResponseField name="title" type="string">Title. Omitted if none has been generated.</ResponseField>
    <ResponseField name="messages" type="array">Full message history, shaped as `[{ role, content }, …]`. Empty for a new thread.</ResponseField>
    <ResponseField name="updated_at" type="string">ISO 8601 timestamp of the last modification.</ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```python Python theme={null}
  import os, requests

  r = requests.get(
      "https://www.orgo.ai/api/chat/threads",
      params={"desktopId": os.environ["COMPUTER_ID"]},
      headers={"Authorization": f"Bearer {os.environ['ORGO_API_KEY']}"},
  )
  for t in r.json()["threads"]:
      print(t["id"], t["status"], t.get("title", "(untitled)"))
  ```

  ```typescript TypeScript theme={null}
  const r = await fetch(
    `https://www.orgo.ai/api/chat/threads?desktopId=${process.env.COMPUTER_ID}`,
    { headers: { Authorization: `Bearer ${process.env.ORGO_API_KEY}` } },
  );
  const { threads } = await r.json();
  ```

  ```bash cURL theme={null}
  curl "https://www.orgo.ai/api/chat/threads?desktopId=$COMPUTER_ID" \
    -H "Authorization: Bearer $ORGO_API_KEY"
  ```
</CodeGroup>

```json theme={null}
{
  "threads": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "remoteId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "status": "regular",
      "title": "GitHub search",
      "messages": [
        { "role": "user", "content": "Open github.com" },
        { "role": "assistant", "content": "Done. GitHub is open." }
      ],
      "updated_at": "2026-04-20T14:22:05.123Z"
    }
  ]
}
```

### Create thread

```http theme={null}
POST /chat/threads
```

Creates an empty thread bound to a computer, and returns `201 Created`.

<ParamField body="desktopId" type="string" required>
  The computer's UUID. Omitting it returns 400.
</ParamField>

<ParamField body="localId" type="string">
  Client-side identifier. Echoed back as `externalId` so clients can reconcile local and remote threads. When omitted, `externalId` is absent from the response.
</ParamField>

<ResponseField name="remoteId" type="string">The new thread's UUID. Use it as `thread_id` in later chat completions.</ResponseField>
<ResponseField name="externalId" type="string">Mirror of `localId`. Omitted if not supplied.</ResponseField>

```bash theme={null}
curl -X POST https://www.orgo.ai/api/chat/threads \
  -H "Authorization: Bearer $ORGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"desktopId": "'"$COMPUTER_ID"'"}'
```

### Get thread

```http theme={null}
GET /chat/threads/{thread_id}
```

Fetches a single thread with its full message history.

<ResponseField name="remoteId" type="string">Thread UUID.</ResponseField>
<ResponseField name="status" type="string">`regular` or `archived`.</ResponseField>
<ResponseField name="title" type="string">Title. Omitted if none has been generated.</ResponseField>
<ResponseField name="messages" type="array">Full message history in chronological order.</ResponseField>

### Update thread

```http theme={null}
PATCH /chat/threads/{thread_id}
```

Updates the title, replaces the messages, or toggles the archive state. Only one thing happens per request: `archive: true` is applied first, otherwise `unarchive: true`, otherwise `title` and `messages` together. A body with none of these fields is accepted and changes nothing.

<ParamField body="title" type="string">
  New title.
</ParamField>

<ParamField body="messages" type="array">
  Replaces the entire stored message history with this array. A full overwrite, not an append.
</ParamField>

<ParamField body="archive" type="boolean">
  `true` sets `status` to `archived`. Nothing else changes: archived threads still appear in the list response, so filter on `status` client-side. Any other value is ignored.
</ParamField>

<ParamField body="unarchive" type="boolean">
  `true` sets `status` back to `regular`. Ignored when `archive: true` is also present. Any other value is ignored.
</ParamField>

<ResponseField name="remoteId" type="string">Thread UUID.</ResponseField>
<ResponseField name="status" type="string">Updated status: `regular` or `archived`.</ResponseField>
<ResponseField name="title" type="string">Updated title. Omitted if unset.</ResponseField>

```bash theme={null}
curl -X PATCH https://www.orgo.ai/api/chat/threads/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $ORGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "GitHub research session"}'
```

### Delete thread

```http theme={null}
DELETE /chat/threads/{thread_id}
```

Permanently deletes the thread and its message history. Prefer `PATCH` with `archive: true` if you might need the conversation back.

```json theme={null}
{ "success": true }
```

### Generate title

```http theme={null}
POST /chat/threads/{thread_id}/title
```

Asks Claude Haiku for a title of three to six words and saves it to the thread. Returns an assistant-ui text stream rather than JSON. The generation is metered against your credits but never blocks: an out-of-credit account still gets a title.

<ParamField body="messages" type="array" required>
  The messages to summarize. Only the first three are read, and the joined text is truncated to 1000 characters. Each message is `{ role, content }` where `content` may be a string or an array of `{ type: "text", text }` blocks. A missing or empty array returns 400.
</ParamField>

Response is `text/plain`:

```text theme={null}
0:"GitHub search session"
```

The title is also persisted, so a later `GET` returns it in `title`.

### Run state

```http theme={null}
GET    /chat/threads/{thread_id}/run
DELETE /chat/threads/{thread_id}/run
```

`GET` reports whether a run is in progress on this thread right now. Use it to decide between rendering a finished transcript and re-attaching to a live one.

<Warning>
  Only runs started from the Orgo dashboard's chat are tracked here. A [`/v1/chat/completions`](/api-reference/chat/completions) request is never registered, so `GET` reports `running: false` for it and `DELETE` cannot stop it.
</Warning>

```json theme={null}
{ "running": true, "startedAt": 1745136000000, "seq": 12 }
```

```json theme={null}
{ "running": false }
```

<Note>
  `running` answers for the single server process that handled your request. With several processes behind the load balancer, a run in progress elsewhere reports `running: false`. Treat `true` as reliable and `false` as "not here".
</Note>

`DELETE` stops a run you started. It returns 200 in both outcomes:

```json theme={null}
{ "stopped": true }
```

```json theme={null}
{ "stopped": false, "reason": "not running on this instance" }
```

Both `/run` methods are restricted to the thread's creator; a workspace teammate gets 403.

### Errors

| Status | Meaning |
| - | - |
| 400 | A required field is missing: `desktopId` on list and create, `messages` on title generation. |
| 401 | The Bearer token starts with `sk_` but is not a known Orgo key: `{ "error": "Invalid API key" }`. A request with no `Authorization` header returns `{ "error": "Authentication required" }`. On `/run`, the access failures listed under 403 are returned as 401 instead. |
| 403 | You are not a member of the computer's workspace (`{ "error": "You do not have access to this workspace." }`), you have view-only access and the request writes (`{ "error": "This workspace is view-only. Ask the owner for write access (workspace_read_only)." }`), or the key is workspace-scoped (`{ "error": "This endpoint requires an account-wide credential (workspace_scope_mismatch)." }`). On `/run`, also returned when the thread or the run belongs to someone else. On list and create, `{ "error": "Access denied" }` means no computer has that `desktopId`. |
| 404 | The thread does not exist. |
| 500 | Unexpected server error. Also returned for a malformed JSON body, and for a `desktopId` or thread id that is not a UUID. Retry with backoff. |
| 503 | Orgo could not verify the credential because of a server-side fault: `{ "error": "Service temporarily unavailable. …" }`, with `Retry-After: 5`. On `/run` the same body comes back as 401. Retry either way. |

Error responses on this surface carry a single string `error` field:

```json theme={null}
{ "error": "desktopId is required" }
```

***

## Using threads with completions

```python theme={null}
history = [{"role": "user", "content": "Open Chrome and go to github.com"}]

# First turn: the server creates the thread and returns its ID.
first = client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=history,
    extra_body={"computer_id": computer_id},
)
thread_id = first.orgo["thread_id"]

history.append({"role": "assistant", "content": first.choices[0].message.content})
history.append({"role": "user", "content": "Search for 'orgo'"})

# Later turn: resend the whole transcript, and reuse the thread ID so the
# stored copy stays complete.
client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=history,
    extra_body={"computer_id": computer_id, "thread_id": thread_id},
)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.