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 '{
"organization_id": "<your-org-id>",
"content": [
{"type": "text", "text": "hello world"}
]
}'
The response is the newly created Chat object:
{
"id": "a1b2c3d4-...",
"organization_id": "...",
"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,
"client_type": "api"
}
The agent begins processing the prompt asynchronously. Use the stream endpoint to follow its progress.
Core workflow
A typical integration follows three steps:
- Create a chat —
POST /api/experimental/chatswith your prompt. - Stream updates — Open a WebSocket to
GET /api/experimental/chats/{chat}/streamto receive real-time events as the agent works. - Send follow-ups —
POST /api/experimental/chats/{chat}/messagesto add messages to the conversation. Messages are queued if the agent is busy.
Endpoints
Create a chat
POST /api/experimental/chats
| Field | Type | Required | Description |
|---|---|---|---|
content | ChatInputPart[] | yes | The user's prompt as one or more content parts. |
organization_id | uuid | yes | The organization this chat belongs to. |
workspace_id | uuid | no | Pin the chat to a specific workspace. |
model_config_id | uuid | no | Override the default model configuration. |
mcp_server_ids | uuid[] | no | Attach MCP servers to this chat. |
labels | map[string]string | no | Key-value labels for the chat (max 50). |
client_type | string | no | "ui" or "api". Defaults to "api". |
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
| Field | Type | Required | Description |
|---|---|---|---|
content | ChatInputPart[] | yes | The follow-up message content. |
model_config_id | uuid | no | Override the model for this turn. |
mcp_server_ids | uuid[] | no | Override 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.
| Field | Type | Required | Description |
|---|---|---|---|
content | ChatInputPart[] | yes | The replacement message content. |
The response is an EditChatMessageResponse with the edited message
and an optional warnings array. When file references in the edited
content cannot be linked (e.g. the per-chat file cap is reached), the
edit still succeeds and the warnings array describes which files
were not linked.
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 parameter | Type | Required | Description |
|---|---|---|---|
after_id | int64 | no | Only 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:
| Type | Description |
|---|---|
message_part | A chunk of the agent's response (text, tool call, etc.). |
message | A complete message has been persisted. |
status | The chat status changed (e.g. running, waiting). |
error | An error occurred during processing. |
retry | The server is retrying a failed LLM call (includes backoff). |
queue_update | The 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:
| Kind | Description |
|---|---|
created | A new chat was created. |
status_change | A chat's status changed. |
title_change | A chat's title was updated. |
diff_status_change | A chat's diff/PR status changed. |
deleted | A chat was deleted. |
List chats
GET /api/experimental/chats
Returns all chats owned by the authenticated user. The files field is
populated on POST /chats and GET /chats/{id}. Other endpoints that
return a Chat object omit it.
| Query parameter | Type | Required | Description |
|---|---|---|---|
q | string | no | Search query string. |
label | string | no | Filter 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). The response
includes a files field (ChatFileMetadata[]) containing metadata for
files that have been successfully linked to the chat. File linking is
best-effort; if linking fails, the file remains in message content but
will be absent from this field.
When file linking is skipped (e.g. the per-chat file cap is reached),
POST /chats includes a warnings array on the Chat response and
POST /chats/{chat}/messages includes a warnings array on the
CreateChatMessageResponse. The warnings field is omitempty and
absent when all files are linked successfully.
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.
| Field | Type | Description |
|---|---|---|
title | string | Set a new title. |
archived | bool | true to archive, false to unarchive. Archiving clears pin_order. |
pin_order | int32 | 0 to unpin; >0 on an unpinned chat to pin it; >0 on a pinned chat to reorder. |
labels | map[string]string | Replace 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.
Files referenced in messages are automatically linked to the chat and
appear in the files field on subsequent
GET /api/experimental/chats/{chat} responses.
Chat statuses
| Status | Meaning |
|---|---|
waiting | Idle. Newly created, finished successfully, or interrupted. |
pending | Queued for processing. |
running | Agent is actively working. |
paused | Agent is paused (for example, waiting for user input). |
completed | Agent finished and the task is complete. |
error | Agent encountered an error. |
requires_action | Agent invoked a client-provided tool and needs the result before continuing. |
Configuration
Deployment-wide chat settings are read and written under
/api/experimental/chats/config/*. Reading config requires authentication; writing requires
deployment-admin privileges.
Auto-archive window
Chats whose newest non-deleted message is older than
auto_archive_days are automatically archived by a background job.
Pinned chats and chats belonging to a still-active thread are
exempt. 0 disables the feature; the default is 90.
# Read
curl -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \
https://coder.example.com/api/experimental/chats/config/auto-archive-days
# { "auto_archive_days": 90 }
# Update
curl -X PUT -H "Coder-Session-Token: $CODER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"auto_archive_days": 60}' \
https://coder.example.com/api/experimental/chats/config/auto-archive-days
Accepted range: 0 to 3650 (~10 years).

