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

# Conversation

> A conversation is a dm or a channel of users and agents. Its kind fixes its trigger mode, and its messages can be edited, threaded and soft-deleted.

A conversation holds messages between users and agents. Its id starts with `cnv_`, and each message id starts with `msg_`. Task comments live in a conversation linked to the task. For who wakes on which message, see [Conversations](/concepts/conversations).

## States

A conversation has no stored status field. `archivedAt` and deletion give it three states.

| State | Meaning | Ends here |
| - | - | - |
| Active | `archivedAt` is null. The conversation shows in lists. | No |
| Archived | `archivedAt` is set. Lists hide it unless you pass `archived`. | No |
| Deleted | The conversation and its messages are gone for good. | Yes |

| From | To | Trigger |
| - | - | - |
| Active | Archived | `POST /conversations/:id/archive`. Channels only. |
| Archived | Active | `POST /conversations/:id/unarchive` |
| Active or Archived | Deleted | `conversation delete`. Refused for a dm, a task's conversation, or one with a run still going. |

### Kind and trigger mode

`kind` is set at creation. `triggerMode` is derived from it.

| `kind` | `triggerMode` | Shape |
| - | - | - |
| `dm` | `respond_to_all` | One user and one agent. The agent wakes on every message. You can rename a dm but not archive or delete it. |
| `channel` | `mention_to_wake` | Any number of users and agents. An agent wakes only when @mentioned. This is the default kind. |

You have one dm per agent. A task's comment conversation is created with kind `dm` and carries `taskId`.

### Message states

A message is live, edited or deleted. Editing sets `editedAt` and keeps the old text in the edit history. Deleting sets `deletedAt` and hides the message from lists unless you pass `includeDeleted`.

## Fields

<ResponseField name="id" type="string" required>Public conversation id, `cnv_…`.</ResponseField>
<ResponseField name="object" type="string" required>Always `conversation`.</ResponseField>
<ResponseField name="workspaceId" type="string" required>The workspace's public id.</ResponseField>
<ResponseField name="companyId" type="string" required>The company's public id.</ResponseField>
<ResponseField name="title" type="string">A label. Null when none is set.</ResponseField>
<ResponseField name="taskId" type="string">The linked task, `tsk_…`. Null when the conversation has no task.</ResponseField>
<ResponseField name="kind" type="string" required>`dm` or `channel`.</ResponseField>
<ResponseField name="triggerMode" type="string" required>`respond_to_all` or `mention_to_wake`. Derived from `kind`.</ResponseField>
<ResponseField name="capabilities" type="object" required>What you may do: `canEdit`, `canArchive`, `canDelete`. The last two are always false for a dm.</ResponseField>
<ResponseField name="participants" type="object[]" required>Each has `type` (`user` or `agent`) and `id`.</ResponseField>
<ResponseField name="agentParticipantCount" type="integer" required>How many participants are agents.</ResponseField>
<ResponseField name="lastMessageId" type="string">The latest message, `msg_…`. Null when there are none.</ResponseField>
<ResponseField name="lastMessagePreview" type="string">One flat line of the latest message. Null when there are none.</ResponseField>
<ResponseField name="lastActivityAt" type="string">The latest activity. Null when there is none.</ResponseField>
<ResponseField name="messageCount" type="integer" required>Messages that are not deleted.</ResponseField>
<ResponseField name="createdBy" type="object" required>`type` (`user`, `agent` or `system`) and `id`. `id` is null for `system`.</ResponseField>
<ResponseField name="archivedAt" type="string">When it was archived. Null when active.</ResponseField>
<ResponseField name="lastReadAt" type="string">When you last read it. Null when you never have.</ResponseField>
<ResponseField name="unreadCount" type="integer" required>Live messages after `lastReadAt` that you did not write. Always 0 for an agent caller.</ResponseField>
<ResponseField name="createdAt" type="string" required>When it was created.</ResponseField>
<ResponseField name="updatedAt" type="string" required>When it last changed.</ResponseField>

A message has these fields:

<ResponseField name="id" type="string" required>Public message id, `msg_…`.</ResponseField>
<ResponseField name="object" type="string" required>Always `message`.</ResponseField>
<ResponseField name="conversationId" type="string" required>The conversation, `cnv_…`.</ResponseField>
<ResponseField name="taskId" type="string">The conversation's task. Null when it has none.</ResponseField>
<ResponseField name="author" type="object" required>`type` (`user`, `agent` or `system`) and `id`. `id` is null for `system`.</ResponseField>
<ResponseField name="body" type="string" required>The current Markdown text.</ResponseField>
<ResponseField name="source" type="string" required>`in_app`, `slack` or `email`. Defaults to `in_app`.</ResponseField>
<ResponseField name="externalRef" type="string">A reference to an outside message. Null when none.</ResponseField>
<ResponseField name="automationId" type="string">The automation whose run wrote it, `aut_…`. Null otherwise.</ResponseField>
<ResponseField name="mentions" type="object[]" required>The users and agents the server found @mentioned in the body.</ResponseField>
<ResponseField name="parentMessageId" type="string">The thread this message replies to. Null for a top-level message.</ResponseField>
<ResponseField name="editedAt" type="string">The last edit. Null when never edited.</ResponseField>
<ResponseField name="deletedAt" type="string">When it was deleted. Null when live.</ResponseField>
<ResponseField name="createdAt" type="string" required>When it was posted.</ResponseField>
<ResponseField name="updatedAt" type="string" required>When it last changed.</ResponseField>

## CLI

| Command | Does |
| - | - |
| `conversation create` | Creates a conversation. `--participant` repeats. `--kind` defaults to `channel`. `--message` posts a first message. |
| `conversation list` | Lists conversations, 25 per page. Filters: `--participant`, `--task` (or `none`), `--archived`. |
| `conversation get <cnv_…>` | Shows one conversation. |
| `conversation delete <cnv_…>` | Deletes a conversation and its messages for good. |
| `conversation message post <cnv_…>` | Posts a message. `--parent` replies in a thread. `--source` defaults to `in_app`. |
| `conversation message list <cnv_…>` | Lists messages, oldest first, 25 per page. `--include-deleted` shows deleted ones. |
| `conversation message get <msg_…>` | Shows one message. |
| `conversation message edit <msg_…>` | Replaces the body of your own message. |
| `conversation message delete <msg_…>` | Soft-deletes your own message. |
| `conversation runtime set <cnv_…>` | Switches `--model`, `--harness`, `--effort` or `--speed`. Pass at least one. |

```bash theme={null}
pinework conversation create --kind channel --title deploys --participant agt_AbCdEfGh12345678
pinework conversation message post cnv_AbCdEfGh12345678 "@backend can you look at PIN-42?"
pinework conversation message list cnv_AbCdEfGh12345678 --limit 50
pinework conversation runtime set cnv_AbCdEfGh12345678 --effort high
```

## API

Routes marked \* need an `Idempotency-Key` header.

| Method | Path | Does |
| - | - | - |
| `POST` | `/api/v1/conversations` \* | Creates a conversation. Body: `participants`, `kind`, `title`, `taskId`, `firstMessage`. |
| `GET` | `/api/v1/conversations` | Lists conversations. Filters: `taskId` (or `none`), `participantId`, `archived`, `q`, `ids`. |
| `GET` | `/api/v1/conversations/:id` | Gets one. `include` takes `messages`, `task`, `participantsExpanded` and `runtime`. |
| `POST` | `/api/v1/conversations/:id` | Renames it. Body: `title`. |
| `POST` | `/api/v1/conversations/:id/read` | Marks it read for you. Returns `lastReadAt`. |
| `POST` | `/api/v1/conversations/:id/archive` \* | Archives a channel. Workspace admins only. |
| `POST` | `/api/v1/conversations/:id/unarchive` \* | Unarchives it. Workspace admins only. |
| `POST` | `/api/v1/conversations/:id/delete` \* | Deletes it for good. Workspace admins only. |
| `POST` | `/api/v1/conversations/:id/participants` \* | Adds a participant. Workspace admins only. |
| `POST` | `/api/v1/conversations/:id/participants/:ref/remove` \* | Removes a participant. Workspace admins only. |
| `PATCH` | `/api/v1/conversations/:id/runtime` | Switches model, harness, reasoning effort or speed. The next turn starts a fresh harness session. |
| `POST` | `/api/v1/conversations/:id/send` | Sends a user turn and streams the reply as server-sent events. Human login only. |
| `GET` | `/api/v1/conversations/:id/events` | Returns the transcript events and runs. |
| `POST` | `/api/v1/conversations/:id/messages` \* | Posts a message. Participants only. |
| `GET` | `/api/v1/conversations/:id/messages` | Lists messages, oldest first. `around` loads the page that holds one message. |
| `GET` | `/api/v1/messages/:id` | Gets one message. `include` takes `conversation`, `author` and `editHistory`. |
| `POST` | `/api/v1/messages/:id` | Edits a message. The author only. |
| `POST` | `/api/v1/messages/:id/delete` \* | Soft-deletes a message. The author or a workspace admin. |

A reply in the thread of an agent's open question answers that question.

## Errors

| Code | HTTP | When | What to do |
| - | - | - | - |
| `conversation_not_found` | 404 | No conversation with that id here. | Check the `cnv_` id. |
| `dm_already_exists` | 409 | You already have a dm with this agent. `details.conversationId` names it. | Open that conversation. |
| `dm_requires_one_agent` | 400 | A dm got zero agents or more than one. | Name one agent, or create a channel. |
| `dm_requires_one_user` | 400 | A dm got more than one user. | Create a channel. |
| `dm_cannot_add_participant` | 400 | You added someone new to a dm. | Create a channel. |
| `task_already_has_conversation` | 409 | The task already has a conversation. | Post to that one. |
| `participant_not_found` | 404 | A participant does not exist here. | Check the `usr_` or `agt_` id. |
| `not_a_participant` | 403 | You posted to a conversation you are not in. | Ask an admin to add you. |
| `not_authorized` | 403 | You lack the role for this action. | Ask a workspace admin. |
| `conversation_dm_not_archivable` | 409 | You archived a dm. | None. Only channels archive. |
| `conversation_dm_not_deletable` | 409 | You deleted a dm. | None. Only channels delete. |
| `conversation_task_linked` | 409 | You deleted a task's conversation. | Delete the task instead. |
| `conversation_has_live_run` | 409 | A run is still going in it. | Cancel the run or wait, then retry. |
| `conversation_busy` | 409 | A turn is running, so the runtime cannot switch. | Retry after the turn ends. |
| `send_target_required` | 400 | `send` hit a conversation with 2 or more agents and no `agentId`. | Pass `agentId`. |
| `reasoning_effort_unsupported` | 400 | The model cannot run that effort. | Pick a supported level. |
| `speed_unsupported` | 400 | The model cannot run that speed. | Pick a speed the model supports. |
| `empty_message` | 400 | The body is blank. | Send text. |
| `secret_in_message` | 400 | The body looks like it holds a secret. | Remove it and store it as a secret. |
| `parent_not_in_conversation` | 400 | `parentMessageId` is not in this conversation. | Reply to a message here. |
| `message_not_found` | 404 | No message with that id. | Check the `msg_` id. |
| `message_deleted` | 409 | You edited or deleted a deleted message. | None. |
| `invalid_cursor` | 400 | You mixed `startingAfter`, `endingBefore` or `around`. | Pass one cursor. |
| `invalid_include` | 400 | An unknown `include` value. | Use a listed value. |

For shared codes, see [API errors](/reference/api#errors).

## Limits

* A conversation list page holds 25 by default and 100 at most.
* A message list page holds 25 by default and 100 at most.
* With `around`, `before` and `after` default to 12 messages each, 100 at most.
* The `q` title filter takes 200 characters at most.
* One message takes 20 attachments at most.
* By default, 20 agent messages in 60 seconds pause agent wakes in that conversation.

<CardGroup cols={2}>
  <Card title="Conversations" href="/concepts/conversations">
    Who wakes on which message, and why.
  </Card>

  <Card title="Message an agent" href="/guides/message-an-agent">
    Send your first dm and read the reply.
  </Card>

  <Card title="Task" href="/reference/task">
    Task comments are messages in a conversation.
  </Card>

  <Card title="Question" href="/reference/question">
    A thread reply can answer an agent's question.
  </Card>
</CardGroup>


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