Chats API

Note

The Chats API is experimental and gated behind the agents experiment flag. Endpoints live under /api/experimental/chats and may change without notice.

The Chats API lets you create and interact with Coder Agents programmatically. You can start a chat, send follow-up messages, and stream the agent's response — all without using the Coder dashboard.

Authentication

All endpoints require a valid session token:

curl -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \ https://coder.example.com/api/experimental/chats

Quick start

Create a chat with a single text prompt:

curl -X POST https://coder.example.com/api/experimental/chats \ -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "content": [ {"type": "text", "text": "hello world"} ] }'

The response is the newly created Chat object:

{ "id": "a1b2c3d4-...", "owner_id": "...", "workspace_id": null, "build_id": null, "agent_id": null, "parent_chat_id": null, "root_chat_id": null, "last_model_config_id": "...", "title": "hello world", "status": "waiting", "last_error": null, "diff_status": null, "created_at": "2025-07-17T00:00:00Z", "updated_at": "2025-07-17T00:00:00Z", "archived": false, "pin_order": 0, "mcp_server_ids": [], "labels": {}, "has_unread": false }

The agent begins processing the prompt asynchronously. Use the stream endpoint to follow its progress.

Core workflow

A typical integration follows three steps:

  1. Create a chatPOST /api/experimental/chats with your prompt.
  2. Stream updates — Open a WebSocket to GET /api/experimental/chats/{chat}/stream to receive real-time events as the agent works.
  3. Send follow-upsPOST /api/experimental/chats/{chat}/messages to add messages to the conversation. Messages are queued if the agent is busy.

Endpoints

Create a chat

POST /api/experimental/chats

FieldTypeRequiredDescription
contentChatInputPart[]yesThe user's prompt as one or more content parts.
workspace_iduuidnoPin the chat to a specific workspace.
model_config_iduuidnoOverride the default model configuration.
mcp_server_idsuuid[]noAttach MCP servers to this chat.
labelsmap[string]stringnoKey-value labels for the chat (max 50).

Each ChatInputPart has a type field. The simplest form is a text part:

{"type": "text", "text": "Fix the failing tests in the auth service"}

Other part types include file (an uploaded image referenced by its file_id) and file-reference (a pointer to a file with optional line range).

Response: 201 Created with a Chat object.

Send a message

POST /api/experimental/chats/{chat}/messages

FieldTypeRequiredDescription
contentChatInputPart[]yesThe follow-up message content.
model_config_iduuidnoOverride the model for this turn.
mcp_server_idsuuid[]noOverride MCP servers for this turn.

If the agent is currently processing, the message is queued automatically. The response indicates whether the message was delivered immediately or queued:

{ "queued": false, "message": { "id": 42, "chat_id": "...", "role": "user", "created_at": "...", "content": [...] } }

When queued is true, message is absent and queued_message is returned instead.

Edit a message

PATCH /api/experimental/chats/{chat}/messages/{message}

Edits a previously sent user message. The agent re-processes from the edited message onward, truncating any messages that followed it.

FieldTypeRequiredDescription
contentChatInputPart[]yesThe replacement message content.

Stream updates

GET /api/experimental/chats/{chat}/stream

Opens a one-way WebSocket connection. The server sends events; clients must not write to the socket (doing so closes the connection).

Query parameterTypeRequiredDescription
after_idint64noOnly return events after this message ID.

Each WebSocket message is a JSON envelope with an outer type ("ping", "data", or "error") and an optional data field. For "data" envelopes the payload is a JSON array of event objects:

{ "type": "data", "data": [ {"type": "status", "chat_id": "...", "status": {"status": "running"}}, {"type": "message_part", "chat_id": "...", "message_part": {"...":"..."}} ] }

Ignore "ping" envelopes (keepalives sent every ~15 s). On first connect the server sends an initial snapshot of the chat state before switching to live events. Use after_id when reconnecting to skip messages the client already has.

Connecting to the stream also updates the caller's read cursor for unread tracking. On disconnect the cursor is advanced to the latest message.

Event types inside each batch:

TypeDescription
message_partA chunk of the agent's response (text, tool call, etc.).
messageA complete message has been persisted.
statusThe chat status changed (e.g. running, waiting).
errorAn error occurred during processing.
retryThe server is retrying a failed LLM call (includes backoff).
queue_updateThe queued message list changed.

Watch all chats

GET /api/experimental/chats/watch

Opens a one-way WebSocket that pushes events for all chats owned by the authenticated user. Use this to drive a sidebar or notification indicator without polling.

Each event is a JSON object with kind and chat fields:

KindDescription
createdA new chat was created.
status_changeA chat's status changed.
title_changeA chat's title was updated.
diff_status_changeA chat's diff/PR status changed.
deletedA chat was deleted.

List chats

GET /api/experimental/chats

Returns all chats owned by the authenticated user.

Query parameterTypeRequiredDescription
qstringnoSearch query string.
labelstringnoFilter by label as key:value. Repeat for multiple (AND logic).

Get a chat

GET /api/experimental/chats/{chat}

Returns the Chat object (metadata only, no messages).

Get chat messages

GET /api/experimental/chats/{chat}/messages

Returns the messages and queued messages for a chat.

List models

GET /api/experimental/chats/models

Returns available models. Use this to discover valid values for model_config_id.

Update a chat

PATCH /api/experimental/chats/{chat}

Updates chat metadata. All fields are optional; omitted fields are left unchanged.

FieldTypeDescription
titlestringSet a new title.
archivedbooltrue to archive, false to unarchive. Archiving clears pin_order.
pin_orderint320 to unpin; >0 on an unpinned chat to pin it; >0 on a pinned chat to reorder.
labelsmap[string]stringReplace all labels. Use null/omit to leave unchanged, {} to clear.

Response: 204 No Content.

Regenerate title

POST /api/experimental/chats/{chat}/title/regenerate

Regenerates the chat title using conversation context. Returns the updated Chat object.

Interrupt

POST /api/experimental/chats/{chat}/interrupt

Stops the agent's current processing loop and returns the chat to waiting status.

Manage queued messages

When a message is queued because the agent is busy, you can manage the queue:

DELETE /api/experimental/chats/{chat}/queue/{queuedMessage}

Removes a queued message before it is processed.

POST /api/experimental/chats/{chat}/queue/{queuedMessage}/promote

Promotes a queued message to be processed next.

Get diff contents

GET /api/experimental/chats/{chat}/diff

Returns the current diff/PR status for a chat, including additions, deletions, changed files, and pull request metadata when available.

File uploads

Attach images to a chat by uploading them first:

curl -X POST "https://coder.example.com/api/experimental/chats/files?organization=$ORG_ID" \ -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \ -H "Content-Type: image/png" \ --data-binary @screenshot.png

The response contains an id you can reference as file_id in a ChatInputPart with "type": "file". To retrieve a previously uploaded file, use GET /api/experimental/chats/files/{file}.

Supported formats: PNG, JPEG, GIF, WebP (up to 10 MB). The server validates actual file content regardless of the declared Content-Type.

Chat statuses

StatusMeaning
waitingIdle — newly created, finished successfully, or interrupted.
pendingQueued for processing.
runningAgent is actively working.
pausedAgent is paused (e.g. waiting for user input).
completedAgent finished and the task is complete.
errorAgent encountered an error.