Skip to Content

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:

KeyReachesMessages are written by
Your own keyYour chatsYou
A service key you created (for example Claude Code)Your chatsThe service, shown by name in the chat
An agent key, a Cohort runtime key, or a key acting as another memberNothing (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" }
FieldTypeDescription
idstringChat ID
titlestringStarts as the first message; rename it any time
statusstringactive or archived
agentobjectThe agent this chat is with. It can’t change; a different agent means a new chat
messageCountintegerMessages in the chat
activeTurnobject | null{ id, status, state } of the turn the agent is working on, or null
lastMessageAt, createdAt, updatedAtstringISO 8601
archivedAtstring | nullISO 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" }
FieldTypeDescription
rolestringhuman (your side) or assistant (the agent)
authorobject{ id, name, displayName, type }. type is human, service or agent
clientMessageIdstring | nullThe idempotency key the message was sent with (human messages)
turnIdstring | nullThe agent turn this message started (human messages)
suggestionsarray{ id, kind, text } next messages the agent suggested (assistant messages)
attachmentsarrayWorkspace 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" }
statusMeaning
pendingThe agent hasn’t answered yet (state is queued, or running once it has picked the turn up). Keep waiting
completereply holds the agent’s message
failedThe agent couldn’t answer; see errorCode
canceledThe chat was archived before the agent answered

List Chats

GET /api/v1/chats
ParameterTypeDescription
statusstringactive (default), archived, or all
limitintegerItems per page (default 25, max 100)
cursorstringCursor 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/chats

Starts 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.

FieldTypeRequiredDescription
agentIdstringYesThe agent’s ID from GET /api/v1/agents
bodystringYesThe message, up to 20,000 characters. May be empty only when fileIds is not
clientMessageIdstringNoIdempotency key (see below)
fileIdsstring[]NoUp 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/:id

Returns the chat, including activeTurn while the agent is answering.


List Messages

GET /api/v1/chats/:id/messages
ParameterTypeDescription
orderstringdesc (newest first, default) or asc (from the start)
limitintegerItems per page (default 25, max 100)
cursorstringCursor from the previous page. Keep order the same while paging

Continue until hasMore is false.


Send a Message

POST /api/v1/chats/:id/messages

Sends 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=25

Returns 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/:id

Titles 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.