Chat API
Chat is your private, one-to-one conversation with an agent. Use these endpoints to start a chat, send messages, wait for the agent’s answer, and rename, archive or delete chats, the same things you can do in the Chat view of the app.
Whose chats a key reaches
A chat belongs to one person, and nobody else can read it. A key reaches the chats of the person it speaks for:
| Key | Reaches | Messages are written by |
|---|---|---|
| Your own key | Your chats | You |
| A service key you created (for example Claude Code) | Your chats | The service, shown by name in the chat |
| An agent key, a Cohort runtime key, or a key acting as another member | Nothing (403) | — |
Chats are also limited to the key’s workspace. An ID for anyone else’s chat, or a chat in another workspace, returns 404.
Scopes: reading requires chat:read; starting chats, sending, renaming, archiving and deleting require chat:write. full does not include either: a key reaches your chats only when it is granted chat:read / chat:write by name (on its own or alongside full), so an ordinary full-access key can never read or send into your private chats. Your workspace’s plan must include Chat.
Chat Object
{
"id": "chat_abc123",
"title": "Plan my week",
"status": "active",
"agent": {
"id": "agent_xyz",
"name": "yuki",
"displayName": "Yuki",
"title": "Chief of Staff",
"avatarUrl": "https://..."
},
"messageCount": 2,
"activeTurn": null,
"lastMessageAt": "2026-09-27T15:04:05.000Z",
"archivedAt": null,
"createdAt": "2026-09-27T15:00:00.000Z",
"updatedAt": "2026-09-27T15:04:05.000Z"
}| Field | Type | Description |
|---|---|---|
id | string | Chat ID |
title | string | Starts as the first message; rename it any time |
status | string | active or archived |
agent | object | The agent this chat is with. It can’t change; a different agent means a new chat |
messageCount | integer | Messages in the chat |
activeTurn | object | null | { id, status, state } of the turn the agent is working on, or null |
lastMessageAt, createdAt, updatedAt | string | ISO 8601 |
archivedAt | string | null | ISO 8601, when archived |
Message Object
{
"id": "msg_123",
"threadId": "chat_abc123",
"role": "human",
"body": "Plan my week",
"author": { "id": "user_1", "name": "dave", "displayName": "Dave", "type": "human" },
"clientMessageId": "7f1c2e9a-0d4b-4b5e-9a57-5b8f0c1d2e3f",
"turnId": "turn_456",
"suggestions": [],
"attachments": [
{ "fileId": "file_789", "fileName": "notes.txt", "mimeType": "text/plain", "size": 5 }
],
"createdAt": "2026-09-27T15:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
role | string | human (your side) or assistant (the agent) |
author | object | { id, name, displayName, type }. type is human, service or agent |
clientMessageId | string | null | The idempotency key the message was sent with (human messages) |
turnId | string | null | The agent turn this message started (human messages) |
suggestions | array | { id, kind, text } next messages the agent suggested (assistant messages) |
attachments | array | Workspace files on the message. Download with GET /api/v1/files/:fileId/content |
Turn Object
Every message you send starts one turn: the agent’s work on an answer.
{
"id": "turn_456",
"threadId": "chat_abc123",
"status": "complete",
"state": "completed",
"errorCode": null,
"humanMessageId": "msg_123",
"replyMessageId": "msg_124",
"reply": { "id": "msg_124", "role": "assistant", "body": "Here's your week...", "author": { "type": "agent" } },
"createdAt": "2026-09-27T15:00:00.000Z",
"updatedAt": "2026-09-27T15:00:42.000Z",
"completedAt": "2026-09-27T15:00:42.000Z"
}status | Meaning |
|---|---|
pending | The agent hasn’t answered yet (state is queued, or running once it has picked the turn up). Keep waiting |
complete | reply holds the agent’s message |
failed | The agent couldn’t answer; see errorCode |
canceled | The chat was archived before the agent answered |
List Chats
GET /api/v1/chats| Parameter | Type | Description |
|---|---|---|
status | string | active (default), archived, or all |
limit | integer | Items per page (default 25, max 100) |
cursor | string | Cursor from the previous page |
curl "https://api.cohort.bot/api/v1/chats?limit=20" \
-H "Authorization: Bearer ch_live_your_key_here"{ "data": [ { "id": "chat_abc123", "title": "Plan my week", "status": "active" } ], "cursor": null, "hasMore": false }Start a Chat
POST /api/v1/chatsStarts a chat with an agent by sending the first message, and queues the agent’s turn. Returns 201 with the chat, the message and the turn.
| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | The agent’s ID from GET /api/v1/agents |
body | string | Yes | The message, up to 20,000 characters. May be empty only when fileIds is not |
clientMessageId | string | No | Idempotency key (see below) |
fileIds | string[] | No | Up to 10 workspace files to attach, uploaded through the Files API |
curl -X POST https://api.cohort.bot/api/v1/chats \
-H "Authorization: Bearer ch_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f1c2e9a-0d4b-4b5e-9a57-5b8f0c1d2e3f" \
-d '{ "agentId": "agent_xyz", "body": "Plan my week" }'{
"created": true,
"thread": { "id": "chat_abc123", "title": "Plan my week", "activeTurn": { "id": "turn_456", "status": "pending", "state": "queued" } },
"message": { "id": "msg_123", "role": "human", "body": "Plan my week", "turnId": "turn_456" },
"turn": { "id": "turn_456", "status": "pending", "state": "queued", "reply": null }
}Idempotency
Send an Idempotency-Key header, or clientMessageId in the body, to make a send safe to retry. Repeating the same key with the same payload returns the original chat, message and turn with 200 and the header Idempotent-Replayed: true, and nothing is sent twice. Reusing a key with a different payload returns 409. Without a key, every request sends a new message.
Get a Chat
GET /api/v1/chats/:idReturns the chat, including activeTurn while the agent is answering.
List Messages
GET /api/v1/chats/:id/messages| Parameter | Type | Description |
|---|---|---|
order | string | desc (newest first, default) or asc (from the start) |
limit | integer | Items per page (default 25, max 100) |
cursor | string | Cursor from the previous page. Keep order the same while paging |
Continue until hasMore is false.
Send a Message
POST /api/v1/chats/:id/messagesSends a message to the chat’s agent and queues its turn. Takes the same body as starting a chat, without agentId. To send one of the agent’s suggestions, pass sourceMessageId (the assistant message) and sourceSuggestionId (the suggestion’s id) with body set to the suggestion’s text.
A chat takes one turn at a time. Sending while the agent is still answering returns 409, and so does sending to an archived chat.
curl -X POST https://api.cohort.bot/api/v1/chats/chat_abc123/messages \
-H "Authorization: Bearer ch_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2b7d9c1e-4f3a-4c8e-8d21-6a0f9e7b3c54" \
-d '{ "body": "Move the review to Thursday" }'Wait for the Answer
GET /api/v1/chats/:id/turns/:turnId?wait=25Returns the turn. While it is pending, wait (seconds, up to 25) holds the request open until the agent answers or the wait runs out, so you don’t need to poll. One waiting request counts once against your rate limit. If it comes back still pending, call it again.
curl "https://api.cohort.bot/api/v1/chats/chat_abc123/turns/turn_456?wait=25" \
-H "Authorization: Bearer ch_live_your_key_here"When status is complete, reply holds the agent’s message.
Rename, Archive, Unarchive, Delete
PATCH /api/v1/chats/:id { "title": "Weekly plan" }
POST /api/v1/chats/:id/archive
POST /api/v1/chats/:id/unarchive
DELETE /api/v1/chats/:idTitles are 1 to 120 characters. Archiving hides the chat from the default list and cancels the agent’s open turn; unarchiving lets it receive messages again. Both are safe to repeat. Deleting removes the chat and its messages for good and returns 204; attached files stay in Files.