Create, read, delete, update, and score the messages in a thread.
A message is one stored node in a thread's conversation tree. The browser reaches these routes on the project's frontend host with an anonymous or provider token, and a server reaches them on https://backend.assistant-api.com with an API key plus the Aui-User-Id and Aui-Workspace-Id headers; see Conventions for hosts, headers, and shared error shapes. Every route is scoped to the caller's workspace.
The message object
| Field | Type | Meaning |
|---|---|---|
id | string | The Assistant Cloud message identifier. |
external_id | string or null | Your id for this message. Use it to repeat a create request for the same message. |
parent_id | string or null | The parent message in the same thread, or null for a root message. |
created_at, updated_at | ISO 8601 | When the message was created and last written. |
format | string | The stored message format, such as aui/v0, ai-sdk/v5, or ai-sdk/v6. |
content | object | The format's message payload. Its serialized JSON is at most 65,536 characters. |
height | integer | The message's depth in the tree. Assistant Cloud calculates it from the parent. |
Create a message
POST /v1/threads/{thread_id}/messages| Field | Type | Required | Rules |
|---|---|---|---|
thread_id | string | yes | 1 to 255 characters. The thread must belong to the caller's workspace. |
parent_id | string or null | yes | The key is required even for a root message. null makes a root. A nonnull value that does not begin with msg_ is coerced to null. |
external_id | string | no | 1 to 255 characters. Repeating it identifies an existing message in this thread. |
parent_external_id | string | no | 1 to 255 characters. When present, it takes precedence over parent_id and must identify a message in this thread. |
format | string | yes | 1 to 255 characters. It is stored as sent. Read conversion recognizes aui/v0, ai-sdk/v5, and ai-sdk/v6. |
content | object | yes | The serialized JSON is at most 65,536 characters. Its role controls user activity tracking and the title trigger. |
Unknown fields are refused.
curl https://backend.assistant-api.com/v1/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages \
-H "Authorization: Bearer $ASSISTANT_API_KEY" \
-H "Aui-User-Id: user_123" \
-H "Aui-Workspace-Id: workspace_123" \
-H "Content-Type: application/json" \
-d '{
"parent_id": null,
"external_id": "client-message-42",
"format": "aui/v0",
"content": {
"role": "user",
"content": [
{ "type": "text", "text": "Show me the latest order." }
]
}
}'{
"parent_id": null,
"external_id": "client-message-42",
"format": "aui/v0",
"content": {
"role": "user",
"content": [
{ "type": "text", "text": "Show me the latest order." }
]
}
}{ "message_id": "msg_…" }| Status | Body | When |
|---|---|---|
201 | { "message_id": "msg_…" } | A new message was inserted. |
200 | { "message_id": "msg_…" } | external_id matched an existing message with the same role. An identical content and format cause no write; a changed body updates the message. |
400 | { "success": false, "error": … } | A required field is missing, a field is invalid, content exceeds 65,536 serialized characters, or an unknown field was sent. |
400 | { "error": "Parent message not found in thread" } | parent_id does not identify a message in this thread. |
400 | { "error": "Parent message with external_id not found in thread" } | parent_external_id does not identify a message in this thread. |
402 | { "error": "plan_limit_reached", "plan": …, "period_end": …, "cap": … } | A new end user sends a user message after the project's active user cap. |
404 | { "error": "Thread not found" } | The thread is not in the caller's workspace. |
404 | { "error": "Project not found" } | The project no longer exists. |
409 | { "error": "Message role cannot change" } | An external_id repeat changes the stored message role. |
401 or 403 | An authentication error. | The caller was not accepted. See Conventions. |
{
"error": "plan_limit_reached",
"plan": "…",
"period_end": "2026-10-01T00:00:00.000Z",
"cap": 200
}Creates with the same external_id are serialized for the thread. A repeat with identical content and format is idempotent. A repeat with changed content or format updates the message and advances the thread's last_message_at; a changed role is refused. Every successful write sets updated_by, and the database calculates height. A user message records the active user. A newly inserted user message on a thread without a title starts a thread title in the background.
List messages
GET /v1/threads/{thread_id}/messages| Query | Type | Default | Rules |
|---|---|---|---|
thread_id | string | 1 to 255 characters. A thread outside the caller's workspace returns an empty list. | |
format | string | 1 to 255 characters. It asks Assistant Cloud to convert recognized stored formats on read. | |
limit | integer | 200 | 1 to 200. |
after | message id | The last id of the previous page. |
Messages are ordered by height, then created_at, then id, all descending. A page continues after the cursor message in that same ordering.
| Requested format | Stored format | Result |
|---|---|---|
ai-sdk/v6 | aui/v0 | Converted to ai-sdk/v6. |
ai-sdk/v6 | ai-sdk/v5 | Converted to ai-sdk/v6. |
ai-sdk/v6 | ai-sdk/v6 | Returned as stored. |
ai-sdk/v5 | aui/v0 | Converted to ai-sdk/v5. |
ai-sdk/v5 | Any other format | Returned as stored. |
| Any other requested format | Any stored format | Returned as stored. |
curl "https://backend.assistant-api.com/v1/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages?limit=200" \
-H "Authorization: Bearer $ASSISTANT_API_KEY" \
-H "Aui-User-Id: user_123" \
-H "Aui-Workspace-Id: workspace_123"{
"messages": [
{
"id": "msg_…",
"external_id": "client-message",
"parent_id": null,
"created_at": "2026-09-17T09:30:00.000Z",
"updated_at": "2026-09-17T09:30:00.000Z",
"format": "aui/v0",
"content": { "role": "user", "content": [] },
"height": 0
}
]
}| Status | Body | When |
|---|---|---|
200 | { "messages": [...] } | Always, including when no messages match or the thread is outside the caller's workspace. |
400 | { "success": false, "error": … } | A query value is out of range. |
401 or 403 | An authentication error. | The caller was not accepted. See Conventions. |
Listing does not write message or thread state. A row that cannot be parsed or converted for the requested format is skipped with a warning, not returned as an error. When attachment storage is configured, recognized attachment URL fields are replaced with signed read URLs that last 15 minutes.
Delete messages
DELETE /v1/threads/{thread_id}/messages| Field | Type | Required | Rules |
|---|---|---|---|
thread_id | string | yes | 1 to 255 characters. |
message_ids | string array | yes | 1 to 200 ids, each 1 to 255 characters. |
curl https://backend.assistant-api.com/v1/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages \
-X DELETE \
-H "Authorization: Bearer $ASSISTANT_API_KEY" \
-H "Aui-User-Id: user_123" \
-H "Aui-Workspace-Id: workspace_123" \
-H "Content-Type: application/json" \
-d '{ "message_ids": ["msg_…"] }'{ "message_ids": ["msg_…"] }{}| Status | Body | When |
|---|---|---|
200 | {} | At least one requested message was deleted. |
400 | { "success": false, "error": … } | message_ids is missing, empty, has more than 200 entries, or contains an invalid id. |
404 | { "error": "Messages not found" } | None of the requested messages were deleted. |
401 or 403 | An authentication error. | The caller was not accepted. See Conventions. |
Deletion is best effort for a batch. A mixed batch answers 200 when at least one id matched, and repeating a request after every matching row is gone answers 404.
Update a message
PUT /v1/threads/{thread_id}/messages/{message_id}| Field | Type | Required | Rules |
|---|---|---|---|
thread_id | string | yes | 1 to 255 characters. |
message_id | string | yes | 1 to 255 characters. |
content | object | yes | Replaces the content. Its serialized JSON is at most 65,536 characters. |
curl https://backend.assistant-api.com/v1/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages/msg_0qzof3jPoDwr7K3agyJN3D4U \
-X PUT \
-H "Authorization: Bearer $ASSISTANT_API_KEY" \
-H "Aui-User-Id: user_123" \
-H "Aui-Workspace-Id: workspace_123" \
-H "Content-Type: application/json" \
-d '{
"content": {
"role": "user",
"content": [
{ "type": "text", "text": "Show me the current order." }
]
}
}'{
"content": {
"role": "user",
"content": [
{ "type": "text", "text": "Show me the current order." }
]
}
}{}| Status | Body | When |
|---|---|---|
200 | {} | The message was updated. |
400 | { "success": false, "error": … } | content is missing or invalid, too large, or an unknown field was sent. |
404 | { "error": "Message not found" } | The message is not in this thread. |
409 | { "error": "Message has children and cannot be updated" } | Another message names this message as its parent. |
401 or 403 | An authentication error. | The caller was not accepted. See Conventions. |
Only a leaf message can be updated. The write records updated_by but does not advance the thread's last_message_at.
Submit message feedback
POST /v1/threads/{thread_id}/messages/{message_id}/feedback| Field | Type | Required | Rules |
|---|---|---|---|
thread_id | string | yes | 1 to 255 characters. |
message_id | string | yes | 1 to 255 characters. |
type | string | yes | positive or negative. |
curl https://backend.assistant-api.com/v1/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages/msg_0qzof3jPoDwr7K3agyJN3D4U/feedback \
-H "Authorization: Bearer $ASSISTANT_API_KEY" \
-H "Aui-User-Id: user_123" \
-H "Aui-Workspace-Id: workspace_123" \
-H "Content-Type: application/json" \
-d '{ "type": "positive" }'{ "type": "positive" }{ "feedback_id": "score_…", "type": "positive" }| Status | Body | When |
|---|---|---|
200 | { "feedback_id": "score_…", "type": "positive" or "negative" } | The feedback score was written. |
400 | { "success": false, "error": … } | type is missing, is not positive or negative, or an unknown field was sent. |
404 | { "error": "Thread not found" } | The thread is not in the caller's workspace. |
404 | { "error": "Message not found" } | The message is not in this thread. |
401 or 403 | An authentication error. | The caller was not accepted. See Conventions. |
Feedback upserts one boolean score named feedback for the message and submitting user. positive stores 1 and negative stores 0. Repeating feedback changes that score instead of adding another row.