Messages
Endpoints for reading and managing the messages in a chat. These routes are not
gated by permission.ai.manage and do not require AI to be enabled on the
tenant. Access is owner-scoped: both Chat and Message expose a read_access
scope that filters by the requester's username, so a message you do not own
resolves as 404.
A message's text lives in parts, a typed-part array (for example
{ "type": "text", "text": "…" }). There is no content, cost, memoryId
text column, or toolUses field. The persisted columns are role, username,
chatId, assistantId, parts, liked, annotations, explanation, and
memoryId, plus the standard timestamps.
Message properties:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Message id |
chatId | string (UUID) | Parent chat id |
assistantId | string (UUID) | null | Assistant that produced the message, when applicable |
role | string | user or assistant |
parts | array | Typed message parts (for example { "type": "text", "text": "…" }) |
liked | boolean | null | User feedback: true, false, or null (no feedback) |
annotations | object | null | Message annotations |
explanation | object | null | Generated explanation, when present |
memoryId | string | null | Associated memory id |
username | string | Owning user |
createdAt / updatedAt | date | Timestamps |
GET /api/chats/{id}/messages
List a chat's messages, ordered by createdAt ascending.
Authentication: Required (session)
Pre-blocks: chat.lookup
Response:
The chat's messages as a HAL collection (inf:chat-messages). Each message is
serialized with its typed parts; the text of a message lives there, not in a
content field.
[
{
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"role": "user",
"annotations": null,
"parts": [
{
"text": "What were the Q4 sales figures?",
"type": "text"
}
],
"chatId": "00000000-0000-4000-8000-000000000001",
"assistantId": null,
"username": "admin",
"liked": null,
"explanation": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"memoryId": null
},
{
"permissions": {},
"id": "00000000-0000-4000-8000-000000000003",
"role": "assistant",
"annotations": null,
"parts": [
{
"text": "Q4 sales reached $2.5M across all regions.",
"type": "text"
}
],
"chatId": "00000000-0000-4000-8000-000000000001",
"assistantId": null,
"username": "admin",
"liked": true,
"explanation": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"memoryId": null
},
{
"permissions": {},
"id": "00000000-0000-4000-8000-000000000004",
"role": "user",
"annotations": null,
"parts": [
{
"text": "Disregard that, never mind.",
"type": "text"
}
],
"chatId": "00000000-0000-4000-8000-000000000001",
"assistantId": null,
"username": "admin",
"liked": null,
"explanation": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"memoryId": null
}
]
DELETE /api/chats/{id}/messages
Clear every message in a chat. The chat itself, including its metadata and settings, is preserved, and the chat is marked stale.
Authentication: Required (session)
Pre-blocks: chat.lookup
Response:
Responds 200 with an empty body (Informer convention via
h.response().code(200)), not 204 No Content.
This operation permanently deletes all messages in the chat. Memories that referenced the deleted messages remain but lose those references.
GET /api/messages/{id}
Retrieve a single message.
Authentication: Required (session)
Pre-blocks: message.lookup
Response:
The message (inf:message), resolved through the Message read_access scope. A
message the requester does not own resolves as 404.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/messages/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"role": "assistant",
"annotations": null,
"parts": [
{
"text": "Q4 sales reached $2.5M across all regions.",
"type": "text"
}
],
"chatId": "00000000-0000-4000-8000-000000000002",
"assistantId": null,
"username": "admin",
"liked": true,
"explanation": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"memoryId": null
}
PUT /api/messages/{id}
Update message feedback. The route accepts only liked.
Authentication: Required (session)
Pre-blocks: message.lookup
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
liked | boolean | null | Yes | true (liked), false (disliked), or null to clear feedback |
Example Request:
{ "liked": true }
Response:
Responds 200 with the updated message. Updating invalidates the chat's prompt
cache.
{
"liked": true
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/messages/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"role": "user",
"annotations": null,
"parts": [
{
"text": "What were the Q4 sales figures?",
"type": "text"
}
],
"chatId": "00000000-0000-4000-8000-000000000002",
"assistantId": null,
"username": "admin",
"liked": true,
"explanation": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"memoryId": null
}
DELETE /api/messages/{id}
Delete a single message and invalidate the chat's prompt cache.
Authentication: Required (session)
Pre-blocks: message.lookup
Response:
Responds 200 with an empty body (Informer convention), not 204 No Content.
Deleting a message that does not exist, or one the requester does not own, is a
no-op that still responds 200.
Deleting a message in the middle of a conversation can affect the context for subsequent AI responses. Consider clearing all messages instead of deleting selectively.